Compare commits

...

90 Commits

Author SHA1 Message Date
Brett Rowan
89641c6316 version: bump version number 2024-12-20 09:26:16 -07:00
Brett Rowan
136ff6a688 feature: add generic hashrate type for doing sums 2024-12-20 09:25:52 -07:00
Brett Rowan
d918d93f4a version: bump version number 2024-12-20 09:21:32 -07:00
Brett Rowan
8046c532a6 bug: fix some issues with adding hashrate types 2024-12-20 09:21:06 -07:00
Upstream Data
92820a362d version: bump version number 2024-12-17 08:15:37 -07:00
aquariuslt
9fd90031a9 fix: update data.env_temp type to float 2024-12-17 08:14:59 -07:00
Brett Rowan
2f2223a112 version: bump version number 2024-12-16 20:06:33 -07:00
Wilfred Allyn
50e6cf9dfd feature: add supports_autotuning to vnish 2024-12-16 07:43:15 -07:00
Wilfred Allyn
1b5e3093e6 feature: set power to highest preset <= wattage 2024-12-16 07:43:15 -07:00
Wilfred Allyn
9e3578b4a2 feature: check vnish presets when set power 2024-12-16 07:43:15 -07:00
Wilfred Allyn
a3087e1a96 set vnish power limit 2024-12-16 07:43:15 -07:00
Brett Rowan
4b16ea2ca2 ci: update publish action 2024-12-10 13:55:30 -07:00
Upstream Data
5dd361c4ef version: bump version number 2024-12-10 13:05:20 -07:00
Upstream Data
098112742c bug: fix vnish config parsing in some overclocking cases 2024-12-10 13:04:58 -07:00
Upstream Data
cd31e0743e docs: update docs 2024-12-10 13:03:07 -07:00
Upstream Data
1a7d0bf7cc feature: add support for vnish S19k pro 2024-12-10 13:02:49 -07:00
Upstream Data
41b5ebf0f0 version: bump version number 2024-12-10 12:28:43 -07:00
Upstream Data
5436bede29 bug: add chip count for avalon 1126 Pro 2024-12-10 12:28:25 -07:00
Upstream Data
c01b3958dc version: bump version number 2024-12-10 11:13:40 -07:00
Upstream Data
c16367285f feature: add support for avalonminer 1126 Pro 2024-12-10 11:13:24 -07:00
Upstream Data
17eae253e6 version: bump version number 2024-12-10 11:08:22 -07:00
Upstream Data
ab30988614 bug: fix incorrect scrypt unit base type 2024-12-10 11:08:03 -07:00
Upstream Data
9b8e547f86 version: bump version number 2024-12-10 11:02:24 -07:00
Upstream Data
3b8cbb9ff1 feature: add support for antminer L9 2024-12-10 11:02:02 -07:00
Upstream Data
d39d278296 version: bump version number 2024-12-10 10:51:58 -07:00
Upstream Data
ea8b922367 bug: fix hive default password 2024-12-10 10:51:46 -07:00
Upstream Data
0d1c8d80e0 version: bump version number 2024-12-10 10:46:44 -07:00
Upstream Data
494d25da97 feature: add support for hiveon S19 2024-12-10 10:46:23 -07:00
Upstream Data
0327d93a35 version: bump version number 2024-12-10 10:29:18 -07:00
Upstream Data
680584c468 bug: fix pydantic validation error with hashrate tuning 2024-12-10 10:28:55 -07:00
Upstream Data
c0dbafb198 version: bump version number 2024-12-10 09:21:34 -07:00
Upstream Data
97d2c4ac34 feature: add more hiveon functionality 2024-12-10 09:21:11 -07:00
Upstream Data
055d633c91 bug: fix hiveon identification in some cases 2024-12-10 08:43:39 -07:00
Upstream Data
68c57f265f feature: add supports presets flag 2024-12-10 07:44:34 -07:00
Upstream Data
1ba0f8ed83 feature: parse vnish presets 2024-12-10 07:44:34 -07:00
Upstream Data
7f74b083d3 feature: add mining mode preset option to config 2024-12-10 07:44:34 -07:00
Wilfred Allyn
97c20dae0a bug: fix boser rpc 2024-12-08 08:54:31 -07:00
Upstream Data
09c7aff640 version: bump version number 2024-12-04 10:05:32 -07:00
Upstream Data
7e7cdc9615 docs: fix some docstrings and type annotations 2024-12-04 10:03:33 -07:00
Upstream Data
b6fb0fd2b9 docs: more documentation improvements and fixes 2024-12-04 09:56:42 -07:00
Upstream Data
46788e7d14 docs: use different docs theme, update docs and examples, and improve docs build 2024-12-04 09:46:01 -07:00
Upstream Data
5b4f84a241 refactor: improve handling of user_suffix in pools 2024-12-04 09:43:01 -07:00
Upstream Data
0c56bfdf9e bug: fix vnish appending scheme to nonexistant pool 2024-12-04 09:32:05 -07:00
Upstream Data
4a5d793cd6 version: bump version number 2024-12-02 15:14:36 -07:00
Upstream Data
1894ff1aea feature: add hiveon web API 2024-12-02 15:14:23 -07:00
Upstream Data
3ab9294000 version: bump version number 2024-12-02 15:12:19 -07:00
Upstream Data
5e0641634b bug: fix MRO for hiveon 2024-12-02 15:11:59 -07:00
Upstream Data
a1975bc9b8 version: bump version number 2024-12-02 13:43:09 -07:00
kdmukai
6a265f03f7 cleanup debugging 2024-12-02 13:42:42 -07:00
Upstream Data
c3658f028f version: bump version number 2024-12-02 10:34:08 -07:00
Upstream Data
ba3c653a29 bug: fix chains: [] which doesnt work with vnish 2024-12-02 10:33:53 -07:00
Upstream Data
61fbc132ed version: bump version number 2024-12-02 10:31:15 -07:00
Upstream Data
3f9f232990 bug: fix pool URL for vnish parser 2024-12-02 10:30:57 -07:00
Upstream Data
29c2398846 version: bump version number 2024-12-02 10:16:23 -07:00
Upstream Data
ecc161820d bug: fix vnish overclock setting 2024-12-02 10:16:04 -07:00
Upstream Data
5fec3052f6 version: bump version number 2024-12-02 09:22:18 -07:00
Upstream Data
437ee774ab bug: type convert to int for vnish config 2024-12-02 09:22:00 -07:00
Upstream Data
aed9e0e406 version: bump version number 2024-12-02 09:14:05 -07:00
Upstream Data
be96428823 bug: skip vnish boards with 0 freq 2024-12-02 09:13:46 -07:00
Upstream Data
446881b237 version: bump version number 2024-12-02 09:00:15 -07:00
Upstream Data
ceab8e55b5 feature: add send_config support for vnish 2024-12-02 08:59:58 -07:00
Upstream Data
e12f85c94d version: bump version number 2024-11-28 15:09:28 -07:00
Upstream Data
0c85f53177 bug: fix some issues with pool URLs 2024-11-28 15:05:04 -07:00
Upstream Data
0b524f9bd0 version: bump version number 2024-11-28 14:40:01 -07:00
Upstream Data
95db852636 docs: update docs 2024-11-28 14:39:46 -07:00
Upstream Data
93fa02412a feature: add support for Hiveon S19j Pro 2024-11-28 14:38:30 -07:00
Upstream Data
edb77e9cd8 bug: fix hiveon identification 2024-11-28 14:33:52 -07:00
Upstream Data
cbf7eeb08d version: bump version number 2024-11-26 08:26:26 -07:00
Upstream Data
d34b35a82d bug: add luxos.supports_shutdown value 2024-11-26 08:25:25 -07:00
Upstream Data
5d57f35475 bug: fix possible case where unidentified miner type can raise and error 2024-11-25 13:00:49 -07:00
Brett Rowan
c95491ea45 version: bump version number 2024-11-22 10:49:47 -07:00
Brett Rowan
e9ec43fac9 bug: fix some more issues with type hints causing warnings 2024-11-22 10:49:24 -07:00
Brett Rowan
42bde081c4 bug: fix some issues with type hints causing warnings 2024-11-22 10:44:09 -07:00
Brett Rowan
bfb72aec1b bug: fix LookupError with AntminerOld 2024-11-22 10:41:34 -07:00
Brett Rowan
b2f36b2f0b bug: fix type hints 2024-11-22 10:41:12 -07:00
Upstream Data
f75c07401b refactor: remove unused imports 2024-11-21 15:32:30 -07:00
Upstream Data
01b96227e0 refactor: more optimizations to hashrate types with metaclass 2024-11-21 15:30:01 -07:00
Upstream Data
82552390c8 refactor: optimize hashrate algo units slightly 2024-11-21 14:55:04 -07:00
Upstream Data
b0651e26b8 version: bump version number 2024-11-21 13:44:48 -07:00
Brett Rowan
c00802e311 feature: add alternate algorithm handlers (#240)
* feature: handle all hashrate algorithm conversions for antminers

* feature: handle all hashrate algorithm conversions for auradine

* feature: handle all hashrate algorithm conversions for avalonminers

* feature: handle all hashrate algorithm conversions for bitaxe

* feature: handle all hashrate algorithm conversions for epic

* feature: handle all hashrate algorithm conversions for goldshell

* refactor: clean up imports

* feature: handle all hashrate algorithm conversions for hammer

* feature: handle all hashrate algorithm conversions for iceriver

* feature: handle all hashrate algorithm conversions for innosilicon

* feature: handle all hashrate algorithm conversions for whatsminer

* tests: update tests to check if miners have board, fan, and algo values

* feature: finish updating all miners with boards, fans, and algos

* feature: update algorithm default values

* feature: add algorithm hashrate values

* feature: improve hashrate types, and use `self.algo` inside miners

---------

Co-authored-by: Upstream Data <brett@upstreamdata.ca>
2024-11-21 13:44:17 -07:00
Brett Rowan
d66739e2c9 feature: swap from @dataclass to pydantic BaseModel (#238)
* feature: switch almost everything to pydantic BaseModel

* feature: swap more dataclasses over to pydantic models

* feature: use more model serializers to make data handle better

* bug: fix some serialization issues with `None` values

* bug: fix some initialization problems with miner mode config

* bug: fix new BOS+ pool parsing

* bug: fix new BOS+ board temperature parsing serialization error

* bug: more misc fixes with serialization, extra methods, and hashrate types

* bug: add explicit type conversions to hashrate types

* bug: fix epic pool URL parsing

* bug: fix positional args in hashboards

* bug: fix epic missing url scheme

* convert board temp to int

---------

Co-authored-by: Upstream Data <brett@upstreamdata.ca>
Co-authored-by: John-Paul Compagnone <jpcompagnone@epicblockchain.io>
2024-11-20 13:42:41 -07:00
Upstream Data
65ed565220 tests: add hammer miner tests 2024-11-20 10:22:53 -07:00
Upstream Data
db6499800b bug: fix raising warnings for partially supported None miners 2024-11-19 14:23:23 -07:00
Upstream Data
cc97ceee61 version: bump version number 2024-11-18 09:01:59 -07:00
Upstream Data
3fa1cb18d9 feature: add support for antminer D7 2024-11-18 08:22:30 -07:00
Upstream Data
cb3c50d007 version: bump version number 2024-11-14 11:16:25 -07:00
Upstream Data
2523ef8484 bug: fix some issues with hammer miners 2024-11-14 11:16:09 -07:00
Upstream Data
01342738b0 version: bump version number 2024-11-14 10:26:50 -07:00
Upstream Data
a9dee4a911 feature: add support for bitaxe gamma 2024-11-14 10:26:00 -07:00
Upstream Data
883ffe20b4 bug: fix bmminer subclass pools data location 2024-11-14 10:25:38 -07:00
237 changed files with 4647 additions and 1134 deletions

View File

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

View File

@@ -7,6 +7,12 @@ repos:
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: check-yaml - 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 - id: check-added-large-files
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 24.10.0 rev: 24.10.0

View File

@@ -1,20 +1,18 @@
# .readthedocs.yaml # .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2 version: 2
# Set the version of Python and other tools you might need # Set the version of Python and other tools you might need
build: build:
os: ubuntu-20.04 os: ubuntu-20.04
tools: tools: { python: "3.11" }
python: "3.9" 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: mkdocs:
configuration: mkdocs.yml configuration: mkdocs.yml
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt

View File

@@ -53,6 +53,8 @@ def backend_str(backend: MinerTypes) -> str:
return "Stock Firmware BitAxe Miners" return "Stock Firmware BitAxe Miners"
case MinerTypes.ICERIVER: case MinerTypes.ICERIVER:
return "Stock Firmware IceRiver Miners" return "Stock Firmware IceRiver Miners"
case MinerTypes.HAMMER:
return "Stock Firmware Hammer Miners"
def create_url_str(mtype: str): def create_url_str(mtype: str):

View File

@@ -11,145 +11,149 @@
[![Read The Docs - Docs](https://img.shields.io/readthedocs/pyasic)](https://pyasic.readthedocs.io/en/latest/) [![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) [![License - Apache 2.0](https://img.shields.io/github/license/UpstreamData/pyasic)](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.
[Click here to view supported miner types](miners/supported_types.md) [Click here to view supported miner types](miners/supported_types.md)
---
## Installation ## 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
--- ---
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 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. 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. The command [`MinerNetwork.scan()`][pyasic.network.MinerNetwork.scan] returns a list that contains any miners found.
```python ```python3
import asyncio # asyncio for handling the async part import asyncio# (1)!
from pyasic.network import MinerNetwork # miner network handles the scanning from pyasic.network import MinerNetwork# (2)!
async def scan_miners(): # define async scan function to allow awaiting async def scan_miners():# (3)!
# create a miner network network = MinerNetwork.from_subnet("192.168.1.50/24")# (4)!
# 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
# scan for miners asynchronously miners = await network.scan()# (5)!
# this will return the correct type of miners if they are supported with all functionality.
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())# (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]. 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. 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
import asyncio # asyncio for handling the async part import asyncio# (1)!
from pyasic import get_miner # handles miner creation from pyasic import get_miner# (2)!
async def get_miners(): # define async scan function to allow awaiting async def get_miners():# (3)!
# get the miner with the miner factory miner_1 = await get_miner("192.168.1.75")# (4)!
# 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") miner_2 = await get_miner("192.168.1.76")
print(miner_1, miner_2) 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")] 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) print(miners)
if __name__ == "__main__": 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 ## 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()`. 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 ### One miner
```python ```python
import asyncio import asyncio# (1)!
from pyasic import get_miner from pyasic import get_miner# (2)!
async def gather_miner_data():
miner = await get_miner("192.168.1.75") async def gather_miner_data():# (3)!
if miner is not None: miner = await get_miner("192.168.1.75")# (4)!
miner_data = await miner.get_data() if miner is not None:# (5)!
print(miner_data) # all data from the dataclass miner_data = await miner.get_data()# (6)!
print(miner_data)# (7)!
print(miner_data.hashrate) # hashrate of the miner in TH/s print(miner_data.hashrate) # hashrate of the miner in TH/s
if __name__ == "__main__": 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. 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# (1)!
from pyasic.network import MinerNetwork # miner network handles the scanning from pyasic.network import MinerNetwork# (2)!
async def gather_miner_data(): # define async scan function to allow awaiting async def gather_miner_data():# (3)!
network = MinerNetwork.from_subnet("192.168.1.50/24") network = MinerNetwork.from_subnet("192.168.1.50/24")# (4)!
miners = await network.scan() 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]) all_miner_data = await asyncio.gather(*[miner.get_data() for miner in miners])
for miner_data in all_miner_data: 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__": 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 ## Miner control
--- ---
`pyasic` exposes a standard interface for each miner using control functions. `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], [`check_light`][pyasic.miners.base.MinerProtocol.check_light],
[`fault_light_off`][pyasic.miners.base.MinerProtocol.fault_light_off], [`fault_light_off`][pyasic.miners.base.MinerProtocol.fault_light_off],
[`fault_light_on`][pyasic.miners.base.MinerProtocol.fault_light_on], [`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 [`send_config`][pyasic.miners.base.MinerProtocol.send_config], and
[`set_power_limit`][pyasic.miners.base.MinerProtocol.set_power_limit]. [`set_power_limit`][pyasic.miners.base.MinerProtocol.set_power_limit].
##### Usage ### Usage
```python ```python
import asyncio import asyncio# (1)!
from pyasic import get_miner from pyasic import get_miner# (2)!
async def set_fault_light(): async def set_fault_light():# (3)!
miner = await get_miner("192.168.1.20") miner = await get_miner("192.168.1.20")# (4)!
# call control function await miner.fault_light_on()# (5)!
await miner.fault_light_on()
if __name__ == "__main__": 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 ## 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()`. `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. 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. [`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. 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: You can use the [`MinerConfig`][pyasic.config.MinerConfig] as follows:
```python ```python
@@ -241,7 +251,6 @@ if __name__ == "__main__":
``` ```
---
## Settings ## 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: `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") settings.update("default_antminer_web_password", "my_pwd")
``` ```
##### Default values: ### Default values:
``` ```
"network_ping_retries": 1, "network_ping_retries": 1,
"network_ping_timeout": 3, "network_ping_timeout": 3,

View File

@@ -302,6 +302,13 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19k Pro (VNish)
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19kPro
handler: python
options:
show_root_heading: false
heading_level: 4
## T19 (VNish) ## T19 (VNish)
::: pyasic.miners.antminer.vnish.X19.T19.VNishT19 ::: pyasic.miners.antminer.vnish.X19.T19.VNishT19
handler: python handler: python
@@ -358,6 +365,20 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19j Pro (Hive)
::: pyasic.miners.antminer.hiveon.X19.S19.HiveonS19jPro
handler: python
options:
show_root_heading: false
heading_level: 4
## S19 (Hive)
::: pyasic.miners.antminer.hiveon.X19.S19.HiveonS19
handler: python
options:
show_root_heading: false
heading_level: 4
## S19 (LuxOS) ## S19 (LuxOS)
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19 ::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19
handler: python handler: python

View File

@@ -15,6 +15,13 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## D7 (Stock)
::: pyasic.miners.antminer.bmminer.X7.D7.BMMinerD7
handler: python
options:
show_root_heading: false
heading_level: 4
## L7 (VNish) ## L7 (VNish)
::: pyasic.miners.antminer.vnish.X7.L7.VnishL7 ::: pyasic.miners.antminer.vnish.X7.L7.VnishL7
handler: python handler: python

View File

@@ -8,6 +8,13 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## D9 (Stock)
::: pyasic.miners.antminer.bmminer.X9.D9.BMMinerD9
handler: python
options:
show_root_heading: false
heading_level: 4
## S9 (Stock) ## S9 (Stock)
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9 ::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9
handler: python handler: python
@@ -36,6 +43,13 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## L9 (Stock)
::: pyasic.miners.antminer.bmminer.X9.L9.BMMinerL9
handler: python
options:
show_root_heading: false
heading_level: 4
## S9 (BOS+) ## S9 (BOS+)
::: pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9 ::: pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9
handler: python handler: python
@@ -43,7 +57,7 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## T9 (Stock) ## T9 (Hive)
::: pyasic.miners.antminer.hiveon.X9.T9.HiveonT9 ::: pyasic.miners.antminer.hiveon.X9.T9.HiveonT9
handler: python handler: python
options: options:

View File

@@ -1,6 +1,13 @@
# pyasic # pyasic
## A11X Models ## A11X Models
## Avalon 1126 Pro (Stock)
::: pyasic.miners.avalonminer.cgminer.A11X.A1126.CGMinerAvalon1126Pro
handler: python
options:
show_root_heading: false
heading_level: 4
## Avalon 1166 Pro (Stock) ## Avalon 1166 Pro (Stock)
::: pyasic.miners.avalonminer.cgminer.A11X.A1166.CGMinerAvalon1166Pro ::: pyasic.miners.avalonminer.cgminer.A11X.A1166.CGMinerAvalon1166Pro
handler: python handler: python

View File

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

View File

@@ -22,3 +22,10 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## Gamma (Stock)
::: pyasic.miners.bitaxe.espminer.BM.BM1370.BitAxeGamma
handler: python
options:
show_root_heading: false
heading_level: 4

View File

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

10
docs/miners/hammer/DX.md Normal file
View File

@@ -0,0 +1,10 @@
# pyasic
## DX Models
## D10 (Stock)
::: pyasic.miners.hammer.blackminer.DX.D10.HammerD10
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -37,16 +37,19 @@ details {
<ul> <ul>
<li><a href="../antminer/X7#l7-stock">L7 (Stock)</a></li> <li><a href="../antminer/X7#l7-stock">L7 (Stock)</a></li>
<li><a href="../antminer/X7#k7-stock">K7 (Stock)</a></li> <li><a href="../antminer/X7#k7-stock">K7 (Stock)</a></li>
<li><a href="../antminer/X7#d7-stock">D7 (Stock)</a></li>
</ul> </ul>
</details> </details>
<details> <details>
<summary>X9 Series:</summary> <summary>X9 Series:</summary>
<ul> <ul>
<li><a href="../antminer/X9#e9pro-stock">E9Pro (Stock)</a></li> <li><a href="../antminer/X9#e9pro-stock">E9Pro (Stock)</a></li>
<li><a href="../antminer/X9#d9-stock">D9 (Stock)</a></li>
<li><a href="../antminer/X9#s9-stock">S9 (Stock)</a></li> <li><a href="../antminer/X9#s9-stock">S9 (Stock)</a></li>
<li><a href="../antminer/X9#s9i-stock">S9i (Stock)</a></li> <li><a href="../antminer/X9#s9i-stock">S9i (Stock)</a></li>
<li><a href="../antminer/X9#s9j-stock">S9j (Stock)</a></li> <li><a href="../antminer/X9#s9j-stock">S9j (Stock)</a></li>
<li><a href="../antminer/X9#t9-stock">T9 (Stock)</a></li> <li><a href="../antminer/X9#t9-stock">T9 (Stock)</a></li>
<li><a href="../antminer/X9#l9-stock">L9 (Stock)</a></li>
</ul> </ul>
</details> </details>
<details> <details>
@@ -375,6 +378,7 @@ details {
<details> <details>
<summary>A11X Series:</summary> <summary>A11X Series:</summary>
<ul> <ul>
<li><a href="../avalonminer/A11X#avalon-1126-pro-stock">Avalon 1126 Pro (Stock)</a></li>
<li><a href="../avalonminer/A11X#avalon-1166-pro-stock">Avalon 1166 Pro (Stock)</a></li> <li><a href="../avalonminer/A11X#avalon-1166-pro-stock">Avalon 1166 Pro (Stock)</a></li>
</ul> </ul>
</details> </details>
@@ -529,6 +533,7 @@ details {
<li><a href="../antminer/X19#s19a-vnish">S19a (VNish)</a></li> <li><a href="../antminer/X19#s19a-vnish">S19a (VNish)</a></li>
<li><a href="../antminer/X19#s19a-pro-vnish">S19a Pro (VNish)</a></li> <li><a href="../antminer/X19#s19a-pro-vnish">S19a Pro (VNish)</a></li>
<li><a href="../antminer/X19#s19-pro-hydro-vnish">S19 Pro Hydro (VNish)</a></li> <li><a href="../antminer/X19#s19-pro-hydro-vnish">S19 Pro Hydro (VNish)</a></li>
<li><a href="../antminer/X19#s19k-pro-vnish">S19k Pro (VNish)</a></li>
<li><a href="../antminer/X19#t19-vnish">T19 (VNish)</a></li> <li><a href="../antminer/X19#t19-vnish">T19 (VNish)</a></li>
</ul> </ul>
</details> </details>
@@ -578,7 +583,14 @@ details {
<details> <details>
<summary>X9 Series:</summary> <summary>X9 Series:</summary>
<ul> <ul>
<li><a href="../antminer/X9#t9-stock">T9 (Stock)</a></li> <li><a href="../antminer/X9#t9-hive">T9 (Hive)</a></li>
</ul>
</details>
<details>
<summary>X19 Series:</summary>
<ul>
<li><a href="../antminer/X19#s19j-pro-hive">S19j Pro (Hive)</a></li>
<li><a href="../antminer/X19#s19-hive">S19 (Hive)</a></li>
</ul> </ul>
</details> </details>
</ul> </ul>
@@ -672,6 +684,7 @@ details {
<li><a href="../bitaxe/BM#supra-stock">Supra (Stock)</a></li> <li><a href="../bitaxe/BM#supra-stock">Supra (Stock)</a></li>
<li><a href="../bitaxe/BM#ultra-stock">Ultra (Stock)</a></li> <li><a href="../bitaxe/BM#ultra-stock">Ultra (Stock)</a></li>
<li><a href="../bitaxe/BM#max-stock">Max (Stock)</a></li> <li><a href="../bitaxe/BM#max-stock">Max (Stock)</a></li>
<li><a href="../bitaxe/BM#gamma-stock">Gamma (Stock)</a></li>
</ul> </ul>
</details> </details>
</ul> </ul>
@@ -694,4 +707,15 @@ details {
</ul> </ul>
</details> </details>
</ul> </ul>
</details>
<details>
<summary>Stock Firmware Hammer Miners:</summary>
<ul>
<details>
<summary>DX Series:</summary>
<ul>
<li><a href="../hammer/DX#d10-stock">D10 (Stock)</a></li>
</ul>
</details>
</ul>
</details> </details>

View File

@@ -1,4 +0,0 @@
jinja2<3.1.4
mkdocs
mkdocstrings[python]
zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability

View File

@@ -1,5 +1,34 @@
site_name: pyasic site_name: pyasic
repo_url: https://github.com/UpstreamData/pyasic repo_url: https://github.com/UpstreamData/pyasic
site_url: !ENV SITE_URL
theme:
name: material
features:
- content.code.copy
- content.code.annotate
palette:
- media: "(prefers-color-scheme)"
toggle:
icon: material/brightness-auto
name: Switch to light mode
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/weather-night
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/weather-sunny
name: Switch to auto mode
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
nav: nav:
- Introduction: "index.md" - Introduction: "index.md"
- Miners: - Miners:
@@ -50,6 +79,7 @@ nav:
- Antminer X17: "miners/antminer/X17.md" - Antminer X17: "miners/antminer/X17.md"
- Antminer X19: "miners/antminer/X19.md" - Antminer X19: "miners/antminer/X19.md"
- Antminer X21: "miners/antminer/X21.md" - Antminer X21: "miners/antminer/X21.md"
- Avalon Nano: "miners/avalonminer/nano.md"
- Avalon 7X: "miners/avalonminer/A7X.md" - Avalon 7X: "miners/avalonminer/A7X.md"
- Avalon 8X: "miners/avalonminer/A8X.md" - Avalon 8X: "miners/avalonminer/A8X.md"
- Avalon 9X: "miners/avalonminer/A9X.md" - Avalon 9X: "miners/avalonminer/A9X.md"
@@ -62,6 +92,7 @@ nav:
- Whatsminer M6X: "miners/whatsminer/M6X.md" - Whatsminer M6X: "miners/whatsminer/M6X.md"
- Innosilicon T3X: "miners/innosilicon/T3X.md" - Innosilicon T3X: "miners/innosilicon/T3X.md"
- Innosilicon A10X: "miners/innosilicon/A10X.md" - Innosilicon A10X: "miners/innosilicon/A10X.md"
- Innosilicon A11X: "miners/innosilicon/A11X.md"
- Goldshell X5: "miners/goldshell/X5.md" - Goldshell X5: "miners/goldshell/X5.md"
- Goldshell XMax: "miners/goldshell/XMax.md" - Goldshell XMax: "miners/goldshell/XMax.md"
- Goldshell XBox: "miners/goldshell/XBox.md" - Goldshell XBox: "miners/goldshell/XBox.md"
@@ -70,6 +101,8 @@ nav:
- Auradine AT: "miners/auradine/AT.md" - Auradine AT: "miners/auradine/AT.md"
- Blockminer: "miners/blockminer/blockminer.md" - Blockminer: "miners/blockminer/blockminer.md"
- BitAxe BM: "miners/bitaxe/BM.md" - BitAxe BM: "miners/bitaxe/BM.md"
- Hammer DX: "miners/hammer/DX.md"
- Iceriver KSX: "miners/iceriver/KSX.md"
- Base Miner: "miners/base_miner.md" - Base Miner: "miners/base_miner.md"
- Settings: - Settings:
- Settings: "settings/settings.md" - Settings: "settings/settings.md"

597
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. # This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
[[package]] [[package]]
name = "aiofiles" name = "aiofiles"
@@ -11,6 +11,17 @@ files = [
{file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"},
] ]
[[package]]
name = "annotated-types"
version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
[[package]] [[package]]
name = "anyio" name = "anyio"
version = "4.6.2.post1" version = "4.6.2.post1"
@@ -57,6 +68,20 @@ pkcs11 = ["python-pkcs11 (>=0.7.0)"]
pyopenssl = ["pyOpenSSL (>=23.0.0)"] pyopenssl = ["pyOpenSSL (>=23.0.0)"]
pywin32 = ["pywin32 (>=227)"] pywin32 = ["pywin32 (>=227)"]
[[package]]
name = "babel"
version = "2.16.0"
description = "Internationalization utilities"
optional = false
python-versions = ">=3.8"
files = [
{file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"},
{file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"},
]
[package.extras]
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
[[package]] [[package]]
name = "betterproto" name = "betterproto"
version = "2.0.0b7" version = "2.0.0b7"
@@ -178,6 +203,120 @@ files = [
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
] ]
[[package]]
name = "charset-normalizer"
version = "3.4.0"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7.0"
files = [
{file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"},
{file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"},
{file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"},
{file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"},
{file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"},
{file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"},
{file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"},
{file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"},
{file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"},
{file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"},
{file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"},
{file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"},
{file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"},
{file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"},
{file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"},
{file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"},
{file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"},
{file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"},
{file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"},
{file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"},
{file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"},
{file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"},
{file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"},
{file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"},
{file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"},
{file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"},
{file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"},
{file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"},
{file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"},
{file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"},
{file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"},
{file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"},
{file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"},
{file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"},
{file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"},
{file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"},
{file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"},
{file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"},
{file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"},
{file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"},
{file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"},
{file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"},
{file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"},
{file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"},
{file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"},
{file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"},
{file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"},
{file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"},
{file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"},
{file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"},
{file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"},
{file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"},
{file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"},
{file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"},
{file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"},
{file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"},
{file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"},
{file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"},
{file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"},
{file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"},
{file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"},
{file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"},
{file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"},
{file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"},
{file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"},
{file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"},
{file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"},
{file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"},
{file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"},
{file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"},
{file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"},
{file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"},
{file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"},
{file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"},
{file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"},
{file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"},
{file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"},
{file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"},
{file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"},
{file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"},
{file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"},
{file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"},
{file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"},
{file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"},
{file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"},
{file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"},
{file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"},
{file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"},
{file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"},
{file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"},
{file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"},
{file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"},
{file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"},
]
[[package]] [[package]]
name = "click" name = "click"
version = "8.1.7" version = "8.1.7"
@@ -380,13 +519,13 @@ files = [
[[package]] [[package]]
name = "httpcore" name = "httpcore"
version = "1.0.6" version = "1.0.7"
description = "A minimal low-level HTTP client." description = "A minimal low-level HTTP client."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"}, {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
{file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
] ]
[package.dependencies] [package.dependencies]
@@ -401,13 +540,13 @@ trio = ["trio (>=0.22.0,<1.0)"]
[[package]] [[package]]
name = "httpx" name = "httpx"
version = "0.27.2" version = "0.28.0"
description = "The next generation HTTP client." description = "The next generation HTTP client."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, {file = "httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc"},
{file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, {file = "httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0"},
] ]
[package.dependencies] [package.dependencies]
@@ -415,7 +554,6 @@ anyio = "*"
certifi = "*" certifi = "*"
httpcore = "==1.*" httpcore = "==1.*"
idna = "*" idna = "*"
sniffio = "*"
[package.extras] [package.extras]
brotli = ["brotli", "brotlicffi"] brotli = ["brotli", "brotlicffi"]
@@ -437,13 +575,13 @@ files = [
[[package]] [[package]]
name = "identify" name = "identify"
version = "2.6.1" version = "2.6.3"
description = "File identification library for Python" description = "File identification library for Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.9"
files = [ files = [
{file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, {file = "identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd"},
{file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, {file = "identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02"},
] ]
[package.extras] [package.extras]
@@ -681,24 +819,68 @@ platformdirs = ">=2.2.0"
pyyaml = ">=5.1" pyyaml = ">=5.1"
[[package]] [[package]]
name = "mkdocstrings" name = "mkdocs-material"
version = "0.20.0" version = "9.5.47"
description = "Automatic documentation from sources, for MkDocs." description = "Documentation that simply works"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
files = [ files = [
{file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, {file = "mkdocs_material-9.5.47-py3-none-any.whl", hash = "sha256:53fb9c9624e7865da6ec807d116cd7be24b3cb36ab31b1d1d1a9af58c56009a2"},
{file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, {file = "mkdocs_material-9.5.47.tar.gz", hash = "sha256:fc3b7a8e00ad896660bd3a5cc12ca0cb28bdc2bcbe2a946b5714c23ac91b0ede"},
] ]
[package.dependencies] [package.dependencies]
babel = ">=2.10,<3.0"
colorama = ">=0.4,<1.0"
jinja2 = ">=3.0,<4.0"
markdown = ">=3.2,<4.0"
mkdocs = ">=1.6,<2.0"
mkdocs-material-extensions = ">=1.3,<2.0"
paginate = ">=0.5,<1.0"
pygments = ">=2.16,<3.0"
pymdown-extensions = ">=10.2,<11.0"
regex = ">=2022.4"
requests = ">=2.26,<3.0"
[package.extras]
git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"]
imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"]
recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"]
[[package]]
name = "mkdocs-material-extensions"
version = "1.3.1"
description = "Extension pack for Python Markdown and MkDocs Material."
optional = false
python-versions = ">=3.8"
files = [
{file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"},
{file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"},
]
[[package]]
name = "mkdocstrings"
version = "0.26.2"
description = "Automatic documentation from sources, for MkDocs."
optional = false
python-versions = ">=3.9"
files = [
{file = "mkdocstrings-0.26.2-py3-none-any.whl", hash = "sha256:1248f3228464f3b8d1a15bd91249ce1701fe3104ac517a5f167a0e01ca850ba5"},
{file = "mkdocstrings-0.26.2.tar.gz", hash = "sha256:34a8b50f1e6cfd29546c6c09fbe02154adfb0b361bb758834bf56aa284ba876e"},
]
[package.dependencies]
click = ">=7.0"
importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""}
Jinja2 = ">=2.11.1" Jinja2 = ">=2.11.1"
Markdown = ">=3.3" Markdown = ">=3.6"
MarkupSafe = ">=1.1" MarkupSafe = ">=1.1"
mkdocs = ">=1.2" mkdocs = ">=1.4"
mkdocs-autorefs = ">=0.3.1" mkdocs-autorefs = ">=1.2"
mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""}
platformdirs = ">=2.2"
pymdown-extensions = ">=6.3" pymdown-extensions = ">=6.3"
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""}
[package.extras] [package.extras]
crystal = ["mkdocstrings-crystal (>=0.3.4)"] crystal = ["mkdocstrings-crystal (>=0.3.4)"]
@@ -707,18 +889,19 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
[[package]] [[package]]
name = "mkdocstrings-python" name = "mkdocstrings-python"
version = "1.8.0" version = "1.12.2"
description = "A Python handler for mkdocstrings." description = "A Python handler for mkdocstrings."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.9"
files = [ files = [
{file = "mkdocstrings_python-1.8.0-py3-none-any.whl", hash = "sha256:4209970cc90bec194568682a535848a8d8489516c6ed4adbe58bbc67b699ca9d"}, {file = "mkdocstrings_python-1.12.2-py3-none-any.whl", hash = "sha256:7f7d40d6db3cb1f5d19dbcd80e3efe4d0ba32b073272c0c0de9de2e604eda62a"},
{file = "mkdocstrings_python-1.8.0.tar.gz", hash = "sha256:1488bddf50ee42c07d9a488dddc197f8e8999c2899687043ec5dd1643d057192"}, {file = "mkdocstrings_python-1.12.2.tar.gz", hash = "sha256:7a1760941c0b52a2cd87b960a9e21112ffe52e7df9d0b9583d04d47ed2e186f3"},
] ]
[package.dependencies] [package.dependencies]
griffe = ">=0.37" griffe = ">=0.49"
mkdocstrings = ">=0.20" mkdocs-autorefs = ">=1.2"
mkdocstrings = ">=0.26"
[[package]] [[package]]
name = "multidict" name = "multidict"
@@ -837,15 +1020,30 @@ files = [
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "24.1" version = "24.2"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
] ]
[[package]]
name = "paginate"
version = "0.5.7"
description = "Divides large result sets into pages for easier browsing"
optional = false
python-versions = "*"
files = [
{file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"},
{file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"},
]
[package.extras]
dev = ["pytest", "tox"]
lint = ["black"]
[[package]] [[package]]
name = "passlib" name = "passlib"
version = "1.7.4" version = "1.7.4"
@@ -936,6 +1134,152 @@ files = [
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
] ]
[[package]]
name = "pydantic"
version = "2.10.3"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"},
{file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
pydantic-core = "2.27.1"
typing-extensions = ">=4.12.2"
[package.extras]
email = ["email-validator (>=2.0.0)"]
timezone = ["tzdata"]
[[package]]
name = "pydantic-core"
version = "2.27.1"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"},
{file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"},
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"},
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"},
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"},
{file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"},
{file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"},
{file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"},
{file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"},
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"},
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"},
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"},
{file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"},
{file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"},
{file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"},
{file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"},
{file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"},
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"},
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"},
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"},
{file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"},
{file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"},
{file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"},
{file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"},
{file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"},
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"},
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"},
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"},
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"},
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"},
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"},
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"},
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"},
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"},
{file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"},
{file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"},
{file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"},
{file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"},
{file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"},
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"},
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"},
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"},
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"},
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"},
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"},
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"},
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"},
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"},
{file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"},
{file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"},
{file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"},
{file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"},
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"},
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"},
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"},
{file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"},
{file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"},
{file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"},
]
[package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pygments"
version = "2.18.0"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
files = [
{file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]] [[package]]
name = "pymdown-extensions" name = "pymdown-extensions"
version = "10.12" version = "10.12"
@@ -1044,6 +1388,130 @@ files = [
[package.dependencies] [package.dependencies]
pyyaml = "*" pyyaml = "*"
[[package]]
name = "regex"
version = "2024.11.6"
description = "Alternative regular expression module, to replace re."
optional = false
python-versions = ">=3.8"
files = [
{file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"},
{file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"},
{file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"},
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"},
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"},
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"},
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"},
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"},
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"},
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"},
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"},
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"},
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"},
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"},
{file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"},
{file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"},
{file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"},
{file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"},
{file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"},
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"},
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"},
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"},
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"},
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"},
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"},
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"},
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"},
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"},
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"},
{file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"},
{file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"},
{file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"},
{file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"},
{file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"},
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"},
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"},
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"},
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"},
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"},
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"},
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"},
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"},
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"},
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"},
{file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"},
{file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"},
{file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"},
{file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"},
{file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"},
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"},
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"},
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"},
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"},
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"},
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"},
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"},
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"},
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"},
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"},
{file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"},
{file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"},
{file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"},
{file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"},
{file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"},
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"},
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"},
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"},
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"},
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"},
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"},
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"},
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"},
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"},
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"},
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"},
{file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"},
{file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"},
{file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"},
{file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"},
{file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"},
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"},
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"},
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"},
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"},
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"},
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"},
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"},
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"},
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"},
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"},
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"},
{file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"},
{file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"},
{file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"},
]
[[package]]
name = "requests"
version = "2.32.3"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.8"
files = [
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
]
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]] [[package]]
name = "six" name = "six"
version = "1.16.0" version = "1.16.0"
@@ -1068,13 +1536,43 @@ files = [
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.0.2" version = "2.2.1"
description = "A lil' TOML parser" description = "A lil' TOML parser"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
] ]
[[package]] [[package]]
@@ -1099,15 +1597,32 @@ files = [
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
] ]
[[package]]
name = "urllib3"
version = "2.2.3"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.8"
files = [
{file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"},
{file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"},
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.27.1" version = "20.28.0"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4"}, {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"},
{file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"}, {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"},
] ]
[package.dependencies] [package.dependencies]
@@ -1163,13 +1678,13 @@ watchmedo = ["PyYAML (>=3.10)"]
[[package]] [[package]]
name = "zipp" name = "zipp"
version = "3.20.2" version = "3.21.0"
description = "Backport of pathlib-compatible object wrapper for zip files" description = "Backport of pathlib-compatible object wrapper for zip files"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.9"
files = [ files = [
{file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"},
{file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"},
] ]
[package.extras] [package.extras]
@@ -1183,4 +1698,4 @@ type = ["pytest-mypy"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "d611b5e8b0c5611d6ee916cedfb7f07f20dfc90a675ebaed04188e8b3c96aabe" content-hash = "c0aa00dd5f3b52bbac53eb765be2bca2ec7f9429e835d6b2fe6bf207f2f39974"

View File

@@ -13,25 +13,28 @@
# 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 dataclasses import asdict, dataclass, field
from pyasic.config.fans import FanModeConfig from pydantic import BaseModel, Field
from pyasic.config.mining import MiningModeConfig
from pyasic.config.fans import FanMode, FanModeConfig, FanModeNormal
from pyasic.config.mining import MiningMode, MiningModeConfig
from pyasic.config.mining.scaling import ScalingConfig from pyasic.config.mining.scaling import ScalingConfig
from pyasic.config.pools import PoolConfig from pyasic.config.pools import PoolConfig
from pyasic.config.temperature import TemperatureConfig from pyasic.config.temperature import TemperatureConfig
from pyasic.misc import merge_dicts from pyasic.misc import merge_dicts
@dataclass class MinerConfig(BaseModel):
class MinerConfig:
"""Represents the configuration for a miner including pool configuration, """Represents the configuration for a miner including pool configuration,
fan mode, temperature settings, mining mode, and power scaling.""" fan mode, temperature settings, mining mode, and power scaling."""
pools: PoolConfig = field(default_factory=PoolConfig.default) class Config:
fan_mode: FanModeConfig = field(default_factory=FanModeConfig.default) arbitrary_types_allowed = True
temperature: TemperatureConfig = field(default_factory=TemperatureConfig.default)
mining_mode: MiningModeConfig = field(default_factory=MiningModeConfig.default) pools: PoolConfig = Field(default_factory=PoolConfig.default)
fan_mode: FanMode = Field(default_factory=FanModeConfig.default)
temperature: TemperatureConfig = Field(default_factory=TemperatureConfig.default)
mining_mode: MiningMode = Field(default_factory=MiningModeConfig.default)
def __getitem__(self, item): def __getitem__(self, item):
try: try:
@@ -41,7 +44,7 @@ class MinerConfig:
def as_dict(self) -> dict: def as_dict(self) -> dict:
"""Converts the MinerConfig object to a dictionary.""" """Converts the MinerConfig object to a dictionary."""
return asdict(self) return self.model_dump()
def as_am_modern(self, user_suffix: str = None) -> dict: def as_am_modern(self, user_suffix: str = None) -> dict:
"""Generates the configuration in the format suitable for modern Antminers.""" """Generates the configuration in the format suitable for modern Antminers."""
@@ -107,7 +110,7 @@ class MinerConfig:
} }
def as_boser(self, user_suffix: str = None) -> dict: def as_boser(self, user_suffix: str = None) -> dict:
""" "Generates the configuration in the format suitable for BOSer.""" """Generates the configuration in the format suitable for BOSer."""
return { return {
**self.fan_mode.as_boser(), **self.fan_mode.as_boser(),
**self.temperature.as_boser(), **self.temperature.as_boser(),
@@ -156,6 +159,19 @@ class MinerConfig:
**self.pools.as_luxos(user_suffix=user_suffix), **self.pools.as_luxos(user_suffix=user_suffix),
} }
def as_vnish(self, user_suffix: str = None) -> dict:
main_cfg = {
"miner": {
**self.fan_mode.as_vnish(),
**self.temperature.as_vnish(),
**self.mining_mode.as_vnish(),
**self.pools.as_vnish(user_suffix=user_suffix),
}
}
if isinstance(self.fan_mode, FanModeNormal):
main_cfg["miner"]["cooling"]["mode"]["param"] = self.temperature.target
return main_cfg
def as_hammer(self, *args, **kwargs) -> dict: def as_hammer(self, *args, **kwargs) -> dict:
return self.as_am_modern(*args, **kwargs) return self.as_am_modern(*args, **kwargs)
@@ -229,13 +245,13 @@ class MinerConfig:
) )
@classmethod @classmethod
def from_vnish(cls, web_settings: dict) -> "MinerConfig": def from_vnish(cls, web_settings: dict, web_presets: list[dict]) -> "MinerConfig":
"""Constructs a MinerConfig object from web settings for VNish miners.""" """Constructs a MinerConfig object from web settings for VNish miners."""
return cls( return cls(
pools=PoolConfig.from_vnish(web_settings), pools=PoolConfig.from_vnish(web_settings),
fan_mode=FanModeConfig.from_vnish(web_settings), fan_mode=FanModeConfig.from_vnish(web_settings),
temperature=TemperatureConfig.from_vnish(web_settings), temperature=TemperatureConfig.from_vnish(web_settings),
mining_mode=MiningModeConfig.from_vnish(web_settings), mining_mode=MiningModeConfig.from_vnish(web_settings, web_presets),
) )
@classmethod @classmethod
@@ -283,3 +299,7 @@ class MinerConfig:
@classmethod @classmethod
def from_hammer(cls, *args, **kwargs) -> "MinerConfig": def from_hammer(cls, *args, **kwargs) -> "MinerConfig":
return cls.from_am_modern(*args, **kwargs) return cls.from_am_modern(*args, **kwargs)
@classmethod
def from_hiveon_modern(cls, web_conf: dict) -> "MinerConfig":
return cls.from_am_modern(web_conf)

View File

@@ -15,9 +15,10 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations from __future__ import annotations
from dataclasses import asdict, dataclass
from enum import Enum from enum import Enum
from pydantic import BaseModel
class MinerConfigOption(Enum): class MinerConfigOption(Enum):
@classmethod @classmethod
@@ -80,14 +81,13 @@ class MinerConfigOption(Enum):
raise KeyError raise KeyError
@dataclass class MinerConfigValue(BaseModel):
class MinerConfigValue:
@classmethod @classmethod
def from_dict(cls, dict_conf: dict | None): def from_dict(cls, dict_conf: dict | None):
return cls() return cls()
def as_dict(self) -> dict: def as_dict(self) -> dict:
return asdict(self) return self.model_dump()
def as_am_modern(self) -> dict: def as_am_modern(self) -> dict:
return {} return {}

View File

@@ -15,14 +15,15 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, field from typing import TypeVar, Union
from pydantic import Field
from pyasic.config.base import MinerConfigOption, MinerConfigValue from pyasic.config.base import MinerConfigOption, MinerConfigValue
@dataclass
class FanModeNormal(MinerConfigValue): class FanModeNormal(MinerConfigValue):
mode: str = field(init=False, default="normal") mode: str = Field(init=False, default="normal")
minimum_fans: int = 1 minimum_fans: int = 1
minimum_speed: int = 0 minimum_speed: int = 0
@@ -86,10 +87,21 @@ class FanModeNormal(MinerConfigValue):
def as_luxos(self) -> dict: def as_luxos(self) -> dict:
return {"fanset": {"speed": -1, "min_fans": self.minimum_fans}} return {"fanset": {"speed": -1, "min_fans": self.minimum_fans}}
def as_vnish(self) -> dict:
return {
"cooling": {
"fan_min_count": self.minimum_fans,
"fan_min_duty": self.minimum_speed,
"mode": {
"name": "auto",
"param": None, # Target temp, must be set later...
},
}
}
@dataclass
class FanModeManual(MinerConfigValue): class FanModeManual(MinerConfigValue):
mode: str = field(init=False, default="manual") mode: str = Field(init=False, default="manual")
speed: int = 100 speed: int = 100
minimum_fans: int = 1 minimum_fans: int = 1
@@ -150,10 +162,21 @@ class FanModeManual(MinerConfigValue):
def as_luxos(self) -> dict: def as_luxos(self) -> dict:
return {"fanset": {"speed": self.speed, "min_fans": self.minimum_fans}} return {"fanset": {"speed": self.speed, "min_fans": self.minimum_fans}}
def as_vnish(self) -> dict:
return {
"cooling": {
"fan_min_count": self.minimum_fans,
"fan_min_duty": self.speed,
"mode": {
"name": "manual",
"param": self.speed, # Speed value
},
}
}
@dataclass
class FanModeImmersion(MinerConfigValue): class FanModeImmersion(MinerConfigValue):
mode: str = field(init=False, default="immersion") mode: str = Field(init=False, default="immersion")
@classmethod @classmethod
def from_dict(cls, dict_conf: dict | None) -> "FanModeImmersion": def from_dict(cls, dict_conf: dict | None) -> "FanModeImmersion":
@@ -176,6 +199,9 @@ class FanModeImmersion(MinerConfigValue):
def as_luxos(self) -> dict: def as_luxos(self) -> dict:
return {"fanset": {"speed": 0, "min_fans": 0}} return {"fanset": {"speed": 0, "min_fans": 0}}
def as_vnish(self) -> dict:
return {"cooling": {"mode": {"name": "immers"}}}
class FanModeConfig(MinerConfigOption): class FanModeConfig(MinerConfigOption):
normal = FanModeNormal normal = FanModeNormal
@@ -273,7 +299,7 @@ class FanModeConfig(MinerConfigOption):
keys = temperature_conf.keys() keys = temperature_conf.keys()
if "auto" in keys: if "auto" in keys:
if "minimumRequiredFans" in keys: if "minimumRequiredFans" in keys:
return cls.normal(temperature_conf["minimumRequiredFans"]) return cls.normal(minimum_fans=temperature_conf["minimumRequiredFans"])
return cls.normal() return cls.normal()
if "manual" in keys: if "manual" in keys:
conf = {} conf = {}
@@ -300,7 +326,9 @@ class FanModeConfig(MinerConfigOption):
mode = web_config["general-config"]["environment-profile"] mode = web_config["general-config"]["environment-profile"]
if mode == "AirCooling": if mode == "AirCooling":
if web_config["advance-config"]["override-fan-control"]: if web_config["advance-config"]["override-fan-control"]:
return cls.manual(web_config["advance-config"]["fan-fixed-percent"]) return cls.manual(
speed=web_config["advance-config"]["fan-fixed-percent"]
)
return cls.normal() return cls.normal()
return cls.immersion() return cls.immersion()
except LookupError: except LookupError:
@@ -333,3 +361,9 @@ class FanModeConfig(MinerConfigOption):
except LookupError: except LookupError:
pass pass
return cls.default() return cls.default()
FanMode = TypeVar(
"FanMode",
bound=Union[FanModeNormal, FanModeManual, FanModeImmersion],
)

View File

@@ -15,7 +15,8 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import field
from typing import TypeVar, Union
from pyasic import settings from pyasic import settings
from pyasic.config.base import MinerConfigOption, MinerConfigValue from pyasic.config.base import MinerConfigOption, MinerConfigValue
@@ -34,11 +35,11 @@ from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
TunerPerformanceMode, TunerPerformanceMode,
) )
from .algo import TunerAlgo from .algo import TunerAlgo, TunerAlgoType
from .presets import MiningPreset
from .scaling import ScalingConfig from .scaling import ScalingConfig
@dataclass
class MiningModeNormal(MinerConfigValue): class MiningModeNormal(MinerConfigValue):
mode: str = field(init=False, default="normal") mode: str = field(init=False, default="normal")
@@ -74,7 +75,6 @@ class MiningModeNormal(MinerConfigValue):
return {"autotunerset": {"enabled": False}} return {"autotunerset": {"enabled": False}}
@dataclass
class MiningModeSleep(MinerConfigValue): class MiningModeSleep(MinerConfigValue):
mode: str = field(init=False, default="sleep") mode: str = field(init=False, default="sleep")
@@ -107,7 +107,6 @@ class MiningModeSleep(MinerConfigValue):
} }
@dataclass
class MiningModeLPM(MinerConfigValue): class MiningModeLPM(MinerConfigValue):
mode: str = field(init=False, default="low") mode: str = field(init=False, default="low")
@@ -130,7 +129,6 @@ class MiningModeLPM(MinerConfigValue):
return {"settings": {"level": 1}} return {"settings": {"level": 1}}
@dataclass
class MiningModeHPM(MinerConfigValue): class MiningModeHPM(MinerConfigValue):
mode: str = field(init=False, default="high") mode: str = field(init=False, default="high")
@@ -150,12 +148,14 @@ class MiningModeHPM(MinerConfigValue):
return {"mode": {"mode": "turbo"}} return {"mode": {"mode": "turbo"}}
@dataclass
class MiningModePowerTune(MinerConfigValue): class MiningModePowerTune(MinerConfigValue):
class Config:
arbitrary_types_allowed = True
mode: str = field(init=False, default="power_tuning") mode: str = field(init=False, default="power_tuning")
power: int = None power: int | None = None
algo: TunerAlgo = field(default_factory=TunerAlgo.default) algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
scaling: ScalingConfig = None scaling: ScalingConfig | None = None
@classmethod @classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune": def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune":
@@ -247,12 +247,14 @@ class MiningModePowerTune(MinerConfigValue):
return {"autotunerset": {"enabled": True}} return {"autotunerset": {"enabled": True}}
@dataclass
class MiningModeHashrateTune(MinerConfigValue): class MiningModeHashrateTune(MinerConfigValue):
class Config:
arbitrary_types_allowed = True
mode: str = field(init=False, default="hashrate_tuning") mode: str = field(init=False, default="hashrate_tuning")
hashrate: int = None hashrate: int | None = None
algo: TunerAlgo = field(default_factory=TunerAlgo.default) algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
scaling: ScalingConfig = None scaling: ScalingConfig | None = None
@classmethod @classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune": def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune":
@@ -343,7 +345,29 @@ class MiningModeHashrateTune(MinerConfigValue):
return {"autotunerset": {"enabled": True}} return {"autotunerset": {"enabled": True}}
@dataclass class MiningModePreset(MinerConfigValue):
mode: str = field(init=False, default="preset")
active_preset: MiningPreset
available_presets: list[MiningPreset] = field(default_factory=list)
def as_vnish(self) -> dict:
return {"overclock": {**self.active_preset.as_vnish()}}
@classmethod
def from_vnish(
cls, web_overclock_settings: dict, web_presets: list[dict]
) -> "MiningModePreset":
active_preset = None
for preset in web_presets:
if preset["name"] == web_overclock_settings["preset"]:
active_preset = preset
return cls(
active_preset=MiningPreset.from_vnish(active_preset),
available_presets=[MiningPreset.from_vnish(p) for p in web_presets],
)
class ManualBoardSettings(MinerConfigValue): class ManualBoardSettings(MinerConfigValue):
freq: float freq: float
volt: float volt: float
@@ -357,8 +381,10 @@ class ManualBoardSettings(MinerConfigValue):
return {"miner-mode": "0"} return {"miner-mode": "0"}
return {"miner-mode": 0} return {"miner-mode": 0}
def as_vnish(self) -> dict:
return {"freq": self.freq}
@dataclass
class MiningModeManual(MinerConfigValue): class MiningModeManual(MinerConfigValue):
mode: str = field(init=False, default="manual") mode: str = field(init=False, default="manual")
@@ -379,6 +405,18 @@ class MiningModeManual(MinerConfigValue):
return {"miner-mode": "0"} return {"miner-mode": "0"}
return {"miner-mode": 0} return {"miner-mode": 0}
def as_vnish(self) -> dict:
chains = [b.as_vnish() for b in self.boards.values() if b.freq != 0]
return {
"overclock": {
"chains": chains if chains != [] else None,
"globals": {
"freq": int(self.global_freq),
"volt": int(self.global_volt),
},
}
}
@classmethod @classmethod
def from_vnish(cls, web_overclock_settings: dict) -> "MiningModeManual": def from_vnish(cls, web_overclock_settings: dict) -> "MiningModeManual":
# will raise KeyError if it cant find the settings, values cannot be empty # will raise KeyError if it cant find the settings, values cannot be empty
@@ -430,6 +468,7 @@ class MiningModeConfig(MinerConfigOption):
sleep = MiningModeSleep sleep = MiningModeSleep
power_tuning = MiningModePowerTune power_tuning = MiningModePowerTune
hashrate_tuning = MiningModeHashrateTune hashrate_tuning = MiningModeHashrateTune
preset = MiningModePreset
manual = MiningModeManual manual = MiningModeManual
@classmethod @classmethod
@@ -521,7 +560,7 @@ class MiningModeConfig(MinerConfigOption):
if autotuning_conf.get("psu_power_limit") is not None: if autotuning_conf.get("psu_power_limit") is not None:
# old autotuning conf # old autotuning conf
return cls.power_tuning( return cls.power_tuning(
autotuning_conf["psu_power_limit"], power=autotuning_conf["psu_power_limit"],
scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"), scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"),
) )
if autotuning_conf.get("mode") is not None: if autotuning_conf.get("mode") is not None:
@@ -530,7 +569,7 @@ class MiningModeConfig(MinerConfigOption):
if mode == "power_target": if mode == "power_target":
if autotuning_conf.get("power_target") is not None: if autotuning_conf.get("power_target") is not None:
return cls.power_tuning( return cls.power_tuning(
autotuning_conf["power_target"], power=autotuning_conf["power_target"],
scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"), scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"),
) )
return cls.power_tuning( return cls.power_tuning(
@@ -539,7 +578,7 @@ class MiningModeConfig(MinerConfigOption):
if mode == "hashrate_target": if mode == "hashrate_target":
if autotuning_conf.get("hashrate_target") is not None: if autotuning_conf.get("hashrate_target") is not None:
return cls.hashrate_tuning( return cls.hashrate_tuning(
autotuning_conf["hashrate_target"], hashrate=autotuning_conf["hashrate_target"],
scaling=ScalingConfig.from_bosminer(toml_conf, mode="hashrate"), scaling=ScalingConfig.from_bosminer(toml_conf, mode="hashrate"),
) )
return cls.hashrate_tuning( return cls.hashrate_tuning(
@@ -547,7 +586,7 @@ class MiningModeConfig(MinerConfigOption):
) )
@classmethod @classmethod
def from_vnish(cls, web_settings: dict): def from_vnish(cls, web_settings: dict, web_presets: list[dict]):
try: try:
mode_settings = web_settings["miner"]["overclock"] mode_settings = web_settings["miner"]["overclock"]
except KeyError: except KeyError:
@@ -556,7 +595,7 @@ class MiningModeConfig(MinerConfigOption):
if mode_settings["preset"] == "disabled": if mode_settings["preset"] == "disabled":
return MiningModeManual.from_vnish(mode_settings) return MiningModeManual.from_vnish(mode_settings)
else: else:
return cls.power_tuning(int(mode_settings["preset"])) return MiningModePreset.from_vnish(mode_settings, web_presets)
@classmethod @classmethod
def from_boser(cls, grpc_miner_conf: dict): def from_boser(cls, grpc_miner_conf: dict):
@@ -571,7 +610,7 @@ class MiningModeConfig(MinerConfigOption):
if tuner_conf["tunerMode"] == 1: if tuner_conf["tunerMode"] == 1:
if tuner_conf.get("powerTarget") is not None: if tuner_conf.get("powerTarget") is not None:
return cls.power_tuning( return cls.power_tuning(
tuner_conf["powerTarget"]["watt"], power=tuner_conf["powerTarget"]["watt"],
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"), scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"),
) )
return cls.power_tuning( return cls.power_tuning(
@@ -581,7 +620,7 @@ class MiningModeConfig(MinerConfigOption):
if tuner_conf["tunerMode"] == 2: if tuner_conf["tunerMode"] == 2:
if tuner_conf.get("hashrateTarget") is not None: if tuner_conf.get("hashrateTarget") is not None:
return cls.hashrate_tuning( return cls.hashrate_tuning(
int(tuner_conf["hashrateTarget"]["terahashPerSecond"]), hashrate=int(tuner_conf["hashrateTarget"]["terahashPerSecond"]),
scaling=ScalingConfig.from_boser( scaling=ScalingConfig.from_boser(
grpc_miner_conf, mode="hashrate" grpc_miner_conf, mode="hashrate"
), ),
@@ -592,13 +631,13 @@ class MiningModeConfig(MinerConfigOption):
if tuner_conf.get("powerTarget") is not None: if tuner_conf.get("powerTarget") is not None:
return cls.power_tuning( return cls.power_tuning(
tuner_conf["powerTarget"]["watt"], power=tuner_conf["powerTarget"]["watt"],
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"), scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"),
) )
if tuner_conf.get("hashrateTarget") is not None: if tuner_conf.get("hashrateTarget") is not None:
return cls.hashrate_tuning( return cls.hashrate_tuning(
int(tuner_conf["hashrateTarget"]["terahashPerSecond"]), hashrate=int(tuner_conf["hashrateTarget"]["terahashPerSecond"]),
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="hashrate"), scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="hashrate"),
) )
@@ -617,9 +656,9 @@ class MiningModeConfig(MinerConfigOption):
if mode_data.get("Mode") == "turbo": if mode_data.get("Mode") == "turbo":
return cls.high() return cls.high()
if mode_data.get("Ths") is not None: if mode_data.get("Ths") is not None:
return cls.hashrate_tuning(mode_data["Ths"]) return cls.hashrate_tuning(hashrate=mode_data["Ths"])
if mode_data.get("Power") is not None: if mode_data.get("Power") is not None:
return cls.power_tuning(mode_data["Power"]) return cls.power_tuning(power=mode_data["Power"])
except LookupError: except LookupError:
return cls.default() return cls.default()
@@ -647,3 +686,18 @@ class MiningModeConfig(MinerConfigOption):
except LookupError: except LookupError:
pass pass
return cls.default() return cls.default()
MiningMode = TypeVar(
"MiningMode",
bound=Union[
MiningModeNormal,
MiningModeHPM,
MiningModeLPM,
MiningModeSleep,
MiningModeManual,
MiningModePowerTune,
MiningModeHashrateTune,
MiningModePreset,
],
)

View File

@@ -1,11 +1,11 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import TypeVar, Union
from pyasic.config.base import MinerConfigOption, MinerConfigValue from pyasic.config.base import MinerConfigOption, MinerConfigValue
@dataclass
class StandardTuneAlgo(MinerConfigValue): class StandardTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard") mode: str = field(init=False, default="standard")
@@ -13,7 +13,6 @@ class StandardTuneAlgo(MinerConfigValue):
return VOptAlgo().as_epic() return VOptAlgo().as_epic()
@dataclass
class VOptAlgo(MinerConfigValue): class VOptAlgo(MinerConfigValue):
mode: str = field(init=False, default="voltage_optimizer") mode: str = field(init=False, default="voltage_optimizer")
@@ -21,7 +20,6 @@ class VOptAlgo(MinerConfigValue):
return "VoltageOptimizer" return "VoltageOptimizer"
@dataclass
class BoardTuneAlgo(MinerConfigValue): class BoardTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="board_tune") mode: str = field(init=False, default="board_tune")
@@ -29,7 +27,6 @@ class BoardTuneAlgo(MinerConfigValue):
return "BoardTune" return "BoardTune"
@dataclass
class ChipTuneAlgo(MinerConfigValue): class ChipTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="chip_tune") mode: str = field(init=False, default="chip_tune")
@@ -37,7 +34,6 @@ class ChipTuneAlgo(MinerConfigValue):
return "ChipTune" return "ChipTune"
@dataclass
class TunerAlgo(MinerConfigOption): class TunerAlgo(MinerConfigOption):
standard = StandardTuneAlgo standard = StandardTuneAlgo
voltage_optimizer = VOptAlgo voltage_optimizer = VOptAlgo
@@ -45,11 +41,11 @@ class TunerAlgo(MinerConfigOption):
chip_tune = ChipTuneAlgo chip_tune = ChipTuneAlgo
@classmethod @classmethod
def default(cls): def default(cls) -> TunerAlgoType:
return cls.standard() return cls.standard()
@classmethod @classmethod
def from_dict(cls, dict_conf: dict | None): def from_dict(cls, dict_conf: dict | None) -> TunerAlgoType:
mode = dict_conf.get("mode") mode = dict_conf.get("mode")
if mode is None: if mode is None:
return cls.default() return cls.default()
@@ -57,3 +53,14 @@ class TunerAlgo(MinerConfigOption):
cls_attr = getattr(cls, mode) cls_attr = getattr(cls, mode)
if cls_attr is not None: if cls_attr is not None:
return cls_attr().from_dict(dict_conf) return cls_attr().from_dict(dict_conf)
TunerAlgoType = TypeVar(
"TunerAlgoType",
bound=Union[
StandardTuneAlgo,
VOptAlgo,
BoardTuneAlgo,
ChipTuneAlgo,
],
)

View File

@@ -0,0 +1,34 @@
from pyasic.config.base import MinerConfigValue
class MiningPreset(MinerConfigValue):
name: str | None = None
power: int | None = None
hashrate: int | None = None
tuned: bool | None = None
modded_psu: bool = False
def as_vnish(self) -> dict:
if self.name is not None:
return {"preset": self.name}
return {}
@classmethod
def from_vnish(cls, web_preset: dict):
name = web_preset["name"]
hr_power_split = web_preset["pretty"].split("~")
if len(hr_power_split) == 1:
power = None
hashrate = None
else:
power = hr_power_split[0].replace("watt", "").strip()
hashrate = hr_power_split[1].replace("TH", "").replace(" LC", "").strip()
tuned = web_preset["status"] == "tuned"
modded_psu = web_preset["modded_psu_required"]
return cls(
name=name,
power=power,
hashrate=hashrate,
tuned=tuned,
modded_psu=modded_psu,
)

View File

@@ -20,7 +20,6 @@ from dataclasses import dataclass
from pyasic.config.base import MinerConfigValue from pyasic.config.base import MinerConfigValue
@dataclass
class ScalingShutdown(MinerConfigValue): class ScalingShutdown(MinerConfigValue):
enabled: bool = False enabled: bool = False
duration: int = None duration: int = None
@@ -35,7 +34,9 @@ class ScalingShutdown(MinerConfigValue):
def from_bosminer(cls, power_scaling_conf: dict): def from_bosminer(cls, power_scaling_conf: dict):
sd_enabled = power_scaling_conf.get("shutdown_enabled") sd_enabled = power_scaling_conf.get("shutdown_enabled")
if sd_enabled is not None: if sd_enabled is not None:
return cls(sd_enabled, power_scaling_conf.get("shutdown_duration")) return cls(
enabled=sd_enabled, duration=power_scaling_conf.get("shutdown_duration")
)
return None return None
@classmethod @classmethod
@@ -43,9 +44,12 @@ class ScalingShutdown(MinerConfigValue):
sd_enabled = power_scaling_conf.get("shutdownEnabled") sd_enabled = power_scaling_conf.get("shutdownEnabled")
if sd_enabled is not None: if sd_enabled is not None:
try: try:
return cls(sd_enabled, power_scaling_conf["shutdownDuration"]["hours"]) return cls(
enabled=sd_enabled,
duration=power_scaling_conf["shutdownDuration"]["hours"],
)
except KeyError: except KeyError:
return cls(sd_enabled) return cls(enabled=sd_enabled)
return None return None
def as_bosminer(self) -> dict: def as_bosminer(self) -> dict:
@@ -60,7 +64,6 @@ class ScalingShutdown(MinerConfigValue):
return {"enable_shutdown": self.enabled, "shutdown_duration": self.duration} return {"enable_shutdown": self.enabled, "shutdown_duration": self.duration}
@dataclass
class ScalingConfig(MinerConfigValue): class ScalingConfig(MinerConfigValue):
step: int = None step: int = None
minimum: int = None minimum: int = None

View File

@@ -17,9 +17,10 @@ from __future__ import annotations
import random import random
import string import string
from dataclasses import dataclass, field
from typing import List from typing import List
from pydantic import Field
from pyasic.config.base import MinerConfigValue from pyasic.config.base import MinerConfigValue
from pyasic.web.braiins_os.proto.braiins.bos.v1 import ( from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
PoolConfiguration, PoolConfiguration,
@@ -30,122 +31,99 @@ from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
) )
@dataclass
class Pool(MinerConfigValue): class Pool(MinerConfigValue):
url: str url: str
user: str user: str
password: str password: str
def as_am_modern(self, user_suffix: str = None) -> dict: def as_am_modern(self, user_suffix: str | None = None) -> dict:
if user_suffix is not None: return {
return { "url": self.url,
"url": self.url, "user": f"{self.user}{user_suffix or ''}",
"user": f"{self.user}{user_suffix}", "pass": self.password,
"pass": self.password, }
}
return {"url": self.url, "user": self.user, "pass": self.password}
def as_wm(self, idx: int = 1, user_suffix: str = None) -> dict: def as_wm(self, idx: int = 1, user_suffix: str | None = None) -> dict:
if user_suffix is not None:
return {
f"pool_{idx}": self.url,
f"worker_{idx}": f"{self.user}{user_suffix}",
f"passwd_{idx}": self.password,
}
return { return {
f"pool_{idx}": self.url, f"pool_{idx}": self.url,
f"worker_{idx}": self.user, f"worker_{idx}": f"{self.user}{user_suffix or ''}",
f"passwd_{idx}": self.password, f"passwd_{idx}": self.password,
} }
def as_am_old(self, idx: int = 1, user_suffix: str = None) -> dict: def as_am_old(self, idx: int = 1, user_suffix: str | None = None) -> dict:
if user_suffix is not None:
return {
f"_ant_pool{idx}url": self.url,
f"_ant_pool{idx}user": f"{self.user}{user_suffix}",
f"_ant_pool{idx}pw": self.password,
}
return { return {
f"_ant_pool{idx}url": self.url, f"_ant_pool{idx}url": self.url,
f"_ant_pool{idx}user": self.user, f"_ant_pool{idx}user": f"{self.user}{user_suffix or ''}",
f"_ant_pool{idx}pw": self.password, f"_ant_pool{idx}pw": self.password,
} }
def as_goldshell(self, user_suffix: str = None) -> dict: def as_goldshell(self, user_suffix: str | None = None) -> dict:
if user_suffix is not None: return {
return { "url": self.url,
"url": self.url, "user": f"{self.user}{user_suffix or ''}",
"user": f"{self.user}{user_suffix}", "pass": self.password,
"pass": self.password, }
}
return {"url": self.url, "user": self.user, "pass": self.password}
def as_avalon(self, user_suffix: str = None) -> str: def as_avalon(self, user_suffix: str | None = None) -> str:
if user_suffix is not None: return ",".join([self.url, f"{self.user}{user_suffix or ''}", self.password])
return ",".join([self.url, f"{self.user}{user_suffix}", self.password])
return ",".join([self.url, self.user, self.password])
def as_inno(self, idx: int = 1, user_suffix: str = None) -> dict: def as_inno(self, idx: int = 1, user_suffix: str | None = None) -> dict:
if user_suffix is not None:
return {
f"Pool{idx}": self.url,
f"UserName{idx}": f"{self.user}{user_suffix}",
f"Password{idx}": self.password,
}
return { return {
f"Pool{idx}": self.url, f"Pool{idx}": self.url,
f"UserName{idx}": self.user, f"UserName{idx}": f"{self.user}{user_suffix or ''}",
f"Password{idx}": self.password, f"Password{idx}": self.password,
} }
def as_bosminer(self, user_suffix: str = None) -> dict: def as_bosminer(self, user_suffix: str | None = None) -> dict:
if user_suffix is not None: return {
return { "url": self.url,
"url": self.url, "user": f"{self.user}{user_suffix or ''}",
"user": f"{self.user}{user_suffix}", "password": self.password,
"password": self.password, }
}
return {"url": self.url, "user": self.user, "password": self.password}
def as_auradine(self, user_suffix: str = None) -> dict: def as_auradine(self, user_suffix: str | None = None) -> dict:
if user_suffix is not None: return {
return { "url": self.url,
"url": self.url, "user": f"{self.user}{user_suffix or ''}",
"user": f"{self.user}{user_suffix}", "pass": self.password,
"pass": self.password, }
}
return {"url": self.url, "user": self.user, "pass": self.password}
def as_epic(self, user_suffix: str = None): def as_epic(self, user_suffix: str | None = None):
if user_suffix is not None: return {
return { "pool": self.url,
"pool": self.url, "login": f"{self.user}{user_suffix or ''}",
"login": f"{self.user}{user_suffix}", "password": self.password,
"password": self.password, }
}
return {"pool": self.url, "login": self.user, "password": self.password}
def as_mara(self, user_suffix: str = None) -> dict: def as_mara(self, user_suffix: str | None = None) -> dict:
if user_suffix is not None: return {
return { "url": self.url,
"url": self.url, "user": f"{self.user}{user_suffix or ''}",
"user": f"{self.user}{user_suffix}", "pass": self.password,
"pass": self.password, }
}
return {"url": self.url, "user": self.user, "pass": self.password}
def as_bitaxe(self, user_suffix: str = None) -> dict: def as_bitaxe(self, user_suffix: str | None = None) -> dict:
return { return {
"stratumURL": self.url, "stratumURL": self.url,
"stratumUser": f"{self.user}{user_suffix}", "stratumUser": f"{self.user}{user_suffix or ''}",
"stratumPassword": self.password, "stratumPassword": self.password,
} }
def as_boser(self) -> PoolConfiguration: def as_boser(self, user_suffix: str | None = None) -> PoolConfiguration:
return PoolConfiguration( return PoolConfiguration(
url=self.url, user=self.user, password=self.password, enabled=True url=self.url,
user=f"{self.user}{user_suffix or ''}",
password=self.password,
enabled=True,
) )
def as_vnish(self, user_suffix: str | None = None) -> dict:
return {
"url": self.url,
"user": f"{self.user}{user_suffix or ''}",
"pass": self.password,
}
@classmethod @classmethod
def from_dict(cls, dict_conf: dict | None) -> "Pool": def from_dict(cls, dict_conf: dict | None) -> "Pool":
return cls( return cls(
@@ -192,7 +170,7 @@ class Pool(MinerConfigValue):
@classmethod @classmethod
def from_vnish(cls, web_pool: dict) -> "Pool": def from_vnish(cls, web_pool: dict) -> "Pool":
return cls( return cls(
url=web_pool["url"], url="stratum+tcp://" + web_pool["url"],
user=web_pool["user"], user=web_pool["user"],
password=web_pool["pass"], password=web_pool["pass"],
) )
@@ -235,11 +213,10 @@ class Pool(MinerConfigValue):
) )
@dataclass
class PoolGroup(MinerConfigValue): class PoolGroup(MinerConfigValue):
pools: list[Pool] = field(default_factory=list) pools: list[Pool] = Field(default_factory=list)
quota: int = 1 quota: int = 1
name: str = None name: str | None = None
def __post_init__(self): def __post_init__(self):
if self.name is None: if self.name is None:
@@ -247,18 +224,18 @@ class PoolGroup(MinerConfigValue):
random.choice(string.ascii_uppercase + string.digits) for _ in range(6) random.choice(string.ascii_uppercase + string.digits) for _ in range(6)
) # generate random pool group name in case it isn't set ) # generate random pool group name in case it isn't set
def as_am_modern(self, user_suffix: str = None) -> list: def as_am_modern(self, user_suffix: str | None = None) -> list:
pools = [] pools = []
idx = 0 idx = 0
while idx < 3: while idx < 3:
if len(self.pools) > idx: if len(self.pools) > idx:
pools.append(self.pools[idx].as_am_modern(user_suffix=user_suffix)) pools.append(self.pools[idx].as_am_modern(user_suffix=user_suffix))
else: else:
pools.append(Pool("", "", "").as_am_modern()) pools.append(Pool(url="", user="", password="").as_am_modern())
idx += 1 idx += 1
return pools return pools
def as_wm(self, user_suffix: str = None) -> dict: def as_wm(self, user_suffix: str | None = None) -> dict:
pools = {} pools = {}
idx = 0 idx = 0
while idx < 3: while idx < 3:
@@ -267,11 +244,11 @@ class PoolGroup(MinerConfigValue):
**self.pools[idx].as_wm(idx=idx + 1, user_suffix=user_suffix) **self.pools[idx].as_wm(idx=idx + 1, user_suffix=user_suffix)
) )
else: else:
pools.update(**Pool("", "", "").as_wm(idx=idx + 1)) pools.update(**Pool(url="", user="", password="").as_wm(idx=idx + 1))
idx += 1 idx += 1
return pools return pools
def as_am_old(self, user_suffix: str = None) -> dict: def as_am_old(self, user_suffix: str | None = None) -> dict:
pools = {} pools = {}
idx = 0 idx = 0
while idx < 3: while idx < 3:
@@ -280,19 +257,21 @@ class PoolGroup(MinerConfigValue):
**self.pools[idx].as_am_old(idx=idx + 1, user_suffix=user_suffix) **self.pools[idx].as_am_old(idx=idx + 1, user_suffix=user_suffix)
) )
else: else:
pools.update(**Pool("", "", "").as_am_old(idx=idx + 1)) pools.update(
**Pool(url="", user="", password="").as_am_old(idx=idx + 1)
)
idx += 1 idx += 1
return pools return pools
def as_goldshell(self, user_suffix: str = None) -> list: def as_goldshell(self, user_suffix: str | None = None) -> list:
return [pool.as_goldshell(user_suffix) for pool in self.pools] return [pool.as_goldshell(user_suffix) for pool in self.pools]
def as_avalon(self, user_suffix: str = None) -> str: def as_avalon(self, user_suffix: str | None = None) -> str:
if len(self.pools) > 0: if len(self.pools) > 0:
return self.pools[0].as_avalon(user_suffix=user_suffix) return self.pools[0].as_avalon(user_suffix=user_suffix)
return Pool("", "", "").as_avalon() return Pool(url="", user="", password="").as_avalon()
def as_inno(self, user_suffix: str = None) -> dict: def as_inno(self, user_suffix: str | None = None) -> dict:
pools = {} pools = {}
idx = 0 idx = 0
while idx < 3: while idx < 3:
@@ -301,11 +280,11 @@ class PoolGroup(MinerConfigValue):
**self.pools[idx].as_inno(idx=idx + 1, user_suffix=user_suffix) **self.pools[idx].as_inno(idx=idx + 1, user_suffix=user_suffix)
) )
else: else:
pools.update(**Pool("", "", "").as_inno(idx=idx + 1)) pools.update(**Pool(url="", user="", password="").as_inno(idx=idx + 1))
idx += 1 idx += 1
return pools return pools
def as_bosminer(self, user_suffix: str = None) -> dict: def as_bosminer(self, user_suffix: str | None = None) -> dict:
if len(self.pools) > 0: if len(self.pools) > 0:
conf = { conf = {
"name": self.name, "name": self.name,
@@ -318,25 +297,28 @@ class PoolGroup(MinerConfigValue):
return conf return conf
return {"name": "Group", "pool": []} return {"name": "Group", "pool": []}
def as_auradine(self, user_suffix: str = None) -> list: def as_auradine(self, user_suffix: str | None = None) -> list:
return [p.as_auradine(user_suffix=user_suffix) for p in self.pools] return [p.as_auradine(user_suffix=user_suffix) for p in self.pools]
def as_epic(self, user_suffix: str = None) -> list: def as_epic(self, user_suffix: str | None = None) -> list:
return [p.as_epic(user_suffix=user_suffix) for p in self.pools] return [p.as_epic(user_suffix=user_suffix) for p in self.pools]
def as_mara(self, user_suffix: str = None) -> list: def as_mara(self, user_suffix: str | None = None) -> list:
return [p.as_mara(user_suffix=user_suffix) for p in self.pools] return [p.as_mara(user_suffix=user_suffix) for p in self.pools]
def as_bitaxe(self, user_suffix: str = None) -> dict: def as_bitaxe(self, user_suffix: str | None = None) -> dict:
return self.pools[0].as_bitaxe(user_suffix=user_suffix) return self.pools[0].as_bitaxe(user_suffix=user_suffix)
def as_boser(self, user_suffix: str = None) -> PoolGroupConfiguration: def as_boser(self, user_suffix: str | None = None) -> PoolGroupConfiguration:
return PoolGroupConfiguration( return PoolGroupConfiguration(
name=self.name, name=self.name,
quota=Quota(value=self.quota), quota=Quota(value=self.quota),
pools=[p.as_boser() for p in self.pools], pools=[p.as_boser() for p in self.pools],
) )
def as_vnish(self, user_suffix: str | None = None) -> dict:
return {"pools": [p.as_vnish(user_suffix=user_suffix) for p in self.pools]}
@classmethod @classmethod
def from_dict(cls, dict_conf: dict | None) -> "PoolGroup": def from_dict(cls, dict_conf: dict | None) -> "PoolGroup":
cls_conf = {} cls_conf = {}
@@ -371,11 +353,11 @@ class PoolGroup(MinerConfigValue):
@classmethod @classmethod
def from_goldshell(cls, web_pools: list) -> "PoolGroup": def from_goldshell(cls, web_pools: list) -> "PoolGroup":
return cls([Pool.from_goldshell(p) for p in web_pools]) return cls(pools=[Pool.from_goldshell(p) for p in web_pools])
@classmethod @classmethod
def from_inno(cls, web_pools: list) -> "PoolGroup": def from_inno(cls, web_pools: list) -> "PoolGroup":
return cls([Pool.from_inno(p) for p in web_pools]) return cls(pools=[Pool.from_inno(p) for p in web_pools])
@classmethod @classmethod
def from_bosminer(cls, toml_group_conf: dict) -> "PoolGroup": def from_bosminer(cls, toml_group_conf: dict) -> "PoolGroup":
@@ -389,7 +371,9 @@ class PoolGroup(MinerConfigValue):
@classmethod @classmethod
def from_vnish(cls, web_settings_pools: dict) -> "PoolGroup": def from_vnish(cls, web_settings_pools: dict) -> "PoolGroup":
return cls([Pool.from_vnish(p) for p in web_settings_pools]) return cls(
pools=[Pool.from_vnish(p) for p in web_settings_pools if p["url"] != ""]
)
@classmethod @classmethod
def from_boser(cls, grpc_pool_group: dict) -> "PoolGroup": def from_boser(cls, grpc_pool_group: dict) -> "PoolGroup":
@@ -424,9 +408,8 @@ class PoolGroup(MinerConfigValue):
) )
@dataclass
class PoolConfig(MinerConfigValue): class PoolConfig(MinerConfigValue):
groups: List[PoolGroup] = field(default_factory=list) groups: List[PoolGroup] = Field(default_factory=list)
@classmethod @classmethod
def default(cls) -> "PoolConfig": def default(cls) -> "PoolConfig":
@@ -448,44 +431,44 @@ class PoolConfig(MinerConfigValue):
group_pools.append(pool) group_pools.append(pool)
return cls(groups=[PoolGroup(pools=group_pools)]) return cls(groups=[PoolGroup(pools=group_pools)])
def as_am_modern(self, user_suffix: str = None) -> dict: def as_am_modern(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0: if len(self.groups) > 0:
return {"pools": self.groups[0].as_am_modern(user_suffix=user_suffix)} return {"pools": self.groups[0].as_am_modern(user_suffix=user_suffix)}
return {"pools": PoolGroup().as_am_modern()} return {"pools": PoolGroup().as_am_modern()}
def as_wm(self, user_suffix: str = None) -> dict: def as_wm(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0: if len(self.groups) > 0:
return {"pools": self.groups[0].as_wm(user_suffix=user_suffix)} return {"pools": self.groups[0].as_wm(user_suffix=user_suffix)}
return {"pools": PoolGroup().as_wm()} return {"pools": PoolGroup().as_wm()}
def as_am_old(self, user_suffix: str = None) -> dict: def as_am_old(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0: if len(self.groups) > 0:
return self.groups[0].as_am_old(user_suffix=user_suffix) return self.groups[0].as_am_old(user_suffix=user_suffix)
return PoolGroup().as_am_old() return PoolGroup().as_am_old()
def as_goldshell(self, user_suffix: str = None) -> dict: def as_goldshell(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0: if len(self.groups) > 0:
return {"pools": self.groups[0].as_goldshell(user_suffix=user_suffix)} return {"pools": self.groups[0].as_goldshell(user_suffix=user_suffix)}
return {"pools": PoolGroup().as_goldshell()} return {"pools": PoolGroup().as_goldshell()}
def as_avalon(self, user_suffix: str = None) -> dict: def as_avalon(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0: if len(self.groups) > 0:
return {"pools": self.groups[0].as_avalon(user_suffix=user_suffix)} return {"pools": self.groups[0].as_avalon(user_suffix=user_suffix)}
return {"pools": PoolGroup().as_avalon()} return {"pools": PoolGroup().as_avalon()}
def as_inno(self, user_suffix: str = None) -> dict: def as_inno(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0: if len(self.groups) > 0:
return self.groups[0].as_inno(user_suffix=user_suffix) return self.groups[0].as_inno(user_suffix=user_suffix)
return PoolGroup().as_inno() return PoolGroup().as_inno()
def as_bosminer(self, user_suffix: str = None) -> dict: def as_bosminer(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0: if len(self.groups) > 0:
return { return {
"group": [g.as_bosminer(user_suffix=user_suffix) for g in self.groups] "group": [g.as_bosminer(user_suffix=user_suffix) for g in self.groups]
} }
return {"group": [PoolGroup().as_bosminer()]} return {"group": [PoolGroup().as_bosminer()]}
def as_boser(self, user_suffix: str = None) -> dict: def as_boser(self, user_suffix: str | None = None) -> dict:
return { return {
"set_pool_groups": SetPoolGroupsRequest( "set_pool_groups": SetPoolGroupsRequest(
save_action=SaveAction.SAVE_AND_APPLY, save_action=SaveAction.SAVE_AND_APPLY,
@@ -493,7 +476,7 @@ class PoolConfig(MinerConfigValue):
) )
} }
def as_auradine(self, user_suffix: str = None) -> dict: def as_auradine(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0: if len(self.groups) > 0:
return { return {
"updatepools": { "updatepools": {
@@ -502,7 +485,7 @@ class PoolConfig(MinerConfigValue):
} }
return {"updatepools": {"pools": PoolGroup().as_auradine()}} return {"updatepools": {"pools": PoolGroup().as_auradine()}}
def as_epic(self, user_suffix: str = None) -> dict: def as_epic(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0: if len(self.groups) > 0:
return { return {
"pools": { "pools": {
@@ -519,17 +502,20 @@ class PoolConfig(MinerConfigValue):
} }
} }
def as_mara(self, user_suffix: str = None) -> dict: def as_mara(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0: if len(self.groups) > 0:
return {"pools": self.groups[0].as_mara(user_suffix=user_suffix)} return {"pools": self.groups[0].as_mara(user_suffix=user_suffix)}
return {"pools": []} return {"pools": []}
def as_bitaxe(self, user_suffix: str = None) -> dict: def as_bitaxe(self, user_suffix: str | None = None) -> dict:
return self.groups[0].as_bitaxe(user_suffix=user_suffix) return self.groups[0].as_bitaxe(user_suffix=user_suffix)
def as_luxos(self, user_suffix: str = None) -> dict: def as_luxos(self, user_suffix: str | None = None) -> dict:
return {} return {}
def as_vnish(self, user_suffix: str | None = None) -> dict:
return self.groups[0].as_vnish(user_suffix=user_suffix)
@classmethod @classmethod
def from_api(cls, api_pools: dict) -> "PoolConfig": def from_api(cls, api_pools: dict) -> "PoolConfig":
try: try:
@@ -538,38 +524,38 @@ class PoolConfig(MinerConfigValue):
return PoolConfig.default() return PoolConfig.default()
pool_data = sorted(pool_data, key=lambda x: int(x["POOL"])) pool_data = sorted(pool_data, key=lambda x: int(x["POOL"]))
return cls([PoolGroup.from_api(pool_data)]) return cls(groups=[PoolGroup.from_api(pool_data)])
@classmethod @classmethod
def from_epic(cls, web_conf: dict) -> "PoolConfig": def from_epic(cls, web_conf: dict) -> "PoolConfig":
pool_data = web_conf["StratumConfigs"] pool_data = web_conf["StratumConfigs"]
return cls([PoolGroup.from_epic(pool_data)]) return cls(groups=[PoolGroup.from_epic(pool_data)])
@classmethod @classmethod
def from_am_modern(cls, web_conf: dict) -> "PoolConfig": def from_am_modern(cls, web_conf: dict) -> "PoolConfig":
pool_data = web_conf["pools"] pool_data = web_conf["pools"]
return cls([PoolGroup.from_am_modern(pool_data)]) return cls(groups=[PoolGroup.from_am_modern(pool_data)])
@classmethod @classmethod
def from_goldshell(cls, web_pools: list) -> "PoolConfig": def from_goldshell(cls, web_pools: list) -> "PoolConfig":
return cls([PoolGroup.from_goldshell(web_pools)]) return cls(groups=[PoolGroup.from_goldshell(web_pools)])
@classmethod @classmethod
def from_inno(cls, web_pools: list) -> "PoolConfig": def from_inno(cls, web_pools: list) -> "PoolConfig":
return cls([PoolGroup.from_inno(web_pools)]) return cls(groups=[PoolGroup.from_inno(web_pools)])
@classmethod @classmethod
def from_bosminer(cls, toml_conf: dict) -> "PoolConfig": def from_bosminer(cls, toml_conf: dict) -> "PoolConfig":
if toml_conf.get("group") is None: if toml_conf.get("group") is None:
return cls() return cls()
return cls([PoolGroup.from_bosminer(g) for g in toml_conf["group"]]) return cls(groups=[PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
@classmethod @classmethod
def from_vnish(cls, web_settings: dict) -> "PoolConfig": def from_vnish(cls, web_settings: dict) -> "PoolConfig":
try: try:
return cls([PoolGroup.from_vnish(web_settings["miner"]["pools"])]) return cls(groups=[PoolGroup.from_vnish(web_settings["miner"]["pools"])])
except LookupError: except LookupError:
return cls() return cls()

View File

@@ -20,11 +20,10 @@ from dataclasses import dataclass
from pyasic.config.base import MinerConfigValue from pyasic.config.base import MinerConfigValue
@dataclass
class TemperatureConfig(MinerConfigValue): class TemperatureConfig(MinerConfigValue):
target: int = None target: int | None = None
hot: int = None hot: int | None = None
danger: int = None danger: int | None = None
@classmethod @classmethod
def default(cls): def default(cls):
@@ -57,6 +56,9 @@ class TemperatureConfig(MinerConfigValue):
def as_luxos(self) -> dict: def as_luxos(self) -> dict:
return {"tempctrlset": [self.target or "", self.hot or "", self.danger or ""]} return {"tempctrlset": [self.target or "", self.hot or "", self.danger or ""]}
def as_vnish(self) -> dict:
return {"misc": {"restart_temp": self.danger}}
@classmethod @classmethod
def from_dict(cls, dict_conf: dict | None) -> "TemperatureConfig": def from_dict(cls, dict_conf: dict | None) -> "TemperatureConfig":
return cls( return cls(
@@ -96,9 +98,16 @@ class TemperatureConfig(MinerConfigValue):
@classmethod @classmethod
def from_vnish(cls, web_settings: dict) -> "TemperatureConfig": def from_vnish(cls, web_settings: dict) -> "TemperatureConfig":
try:
dangerous_temp = web_settings["misc"]["restart_temp"]
except KeyError:
dangerous_temp = None
try: try:
if web_settings["miner"]["cooling"]["mode"]["name"] == "auto": if web_settings["miner"]["cooling"]["mode"]["name"] == "auto":
return cls(target=web_settings["miner"]["cooling"]["mode"]["param"]) return cls(
target=web_settings["miner"]["cooling"]["mode"]["param"],
danger=dangerous_temp,
)
except KeyError: except KeyError:
pass pass
return cls() return cls()

View File

@@ -15,25 +15,24 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import copy import copy
import json
import time import time
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 pydantic import BaseModel, Field, computed_field, field_serializer
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.config.mining import MiningModePowerTune from pyasic.config.mining import MiningModePowerTune
from pyasic.data.pools import PoolMetrics, Scheme from pyasic.data.pools import PoolMetrics, Scheme
from pyasic.device.algorithm.hashrate import AlgoHashRateType
from .boards import HashBoard from .boards import HashBoard
from .device import DeviceInfo from .device import DeviceInfo
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
from .fans import Fan from .fans import Fan
from .hashrate import AlgoHashRate, HashUnit
@dataclass class MinerData(BaseModel):
class MinerData:
"""A Dataclass to standardize data returned from miners (specifically `AnyMiner().get_data()`) """A Dataclass to standardize data returned from miners (specifically `AnyMiner().get_data()`)
Attributes: Attributes:
@@ -50,7 +49,6 @@ class MinerData:
fw_ver: The current firmware version on the miner as a str. fw_ver: The current firmware version on the miner as a str.
hostname: The network hostname of the miner as a str. 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.
expected_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 [`HashBoard`][pyasic.data.HashBoard]s on the miner with their statistics. hashboards: A list of [`HashBoard`][pyasic.data.HashBoard]s on the miner with their statistics.
temperature_avg: The average temperature across the boards. Calculated automatically. temperature_avg: The average temperature across the boards. Calculated automatically.
@@ -77,58 +75,44 @@ class MinerData:
# general # general
ip: str ip: str
_datetime: datetime = field(repr=False, default=None) raw_datetime: datetime = Field(
datetime: str = field(init=False) exclude=True, default_factory=datetime.now(timezone.utc).astimezone, repr=False
timestamp: int = field(init=False) )
# about # about
device_info: DeviceInfo = None device_info: DeviceInfo | None = None
make: str = field(init=False) mac: str | None = None
model: str = field(init=False) api_ver: str | None = None
firmware: str = field(init=False) fw_ver: str | None = None
algo: str = field(init=False) hostname: str | None = None
mac: str = None
api_ver: str = None
fw_ver: str = None
hostname: str = None
# hashrate # hashrate
hashrate: AlgoHashRate = field(init=False) raw_hashrate: AlgoHashRateType = Field(exclude=True, default=None, repr=False)
_hashrate: AlgoHashRate = field(repr=False, default=None)
# expected # expected
expected_hashrate: float = None expected_hashrate: AlgoHashRateType | None = None
expected_hashboards: int = None expected_hashboards: int | None = None
expected_chips: int = None expected_chips: int | None = None
expected_fans: int = None expected_fans: int | None = None
# % expected
percent_expected_chips: float = field(init=False)
percent_expected_hashrate: float = field(init=False)
percent_expected_wattage: float = field(init=False)
# temperature # temperature
temperature_avg: int = field(init=False) env_temp: float | None = None
env_temp: float = None
# power # power
wattage: int = None wattage: int | None = None
wattage_limit: int = field(init=False) voltage: float | None = None
voltage: float = None raw_wattage_limit: int | None = Field(exclude=True, default=None, repr=False)
_wattage_limit: int = field(repr=False, default=None)
# fans # fans
fans: List[Fan] = field(default_factory=list) fans: List[Fan] = Field(default_factory=list)
fan_psu: int = None fan_psu: int | None = None
# boards # boards
hashboards: List[HashBoard] = field(default_factory=list) hashboards: List[HashBoard] = Field(default_factory=list)
total_chips: int = field(init=False)
nominal: bool = field(init=False)
# config # config
config: MinerConfig = None config: MinerConfig | None = None
fault_light: Union[bool, None] = None fault_light: bool | None = None
# errors # errors
errors: List[ errors: List[
@@ -138,30 +122,21 @@ class MinerData:
X19Error, X19Error,
InnosiliconError, InnosiliconError,
] ]
] = field(default_factory=list) ] = Field(default_factory=list)
# mining state # mining state
is_mining: bool = True is_mining: bool = True
uptime: int = None uptime: int | None = None
efficiency: int = field(init=False)
# pools # pools
pools: list[PoolMetrics] = field(default_factory=list) pools: list[PoolMetrics] = Field(default_factory=list)
@classmethod @classmethod
def fields(cls): def fields(cls):
return [f.name for f in fields(cls) if not f.name.startswith("_")] return list(cls.model_fields.keys())
@staticmethod
def dict_factory(x):
return {
k: v.value if isinstance(v, Scheme) else 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.raw_datetime = datetime.now(timezone.utc).astimezone()
def get(self, __key: str, default: Any = None): def get(self, __key: str, default: Any = None):
try: try:
@@ -189,19 +164,19 @@ class MinerData:
def __floordiv__(self, other): def __floordiv__(self, other):
cp = copy.deepcopy(self) cp = copy.deepcopy(self)
for key in self: for key in self.fields():
item = getattr(self, key) item = getattr(self, key)
if isinstance(item, int): if isinstance(item, int):
setattr(cp, key, item // other) setattr(cp, key, item // other)
if isinstance(item, float): if isinstance(item, float):
setattr(cp, key, round(item / other, 2)) setattr(cp, key, item / other)
return cp return cp
def __add__(self, other): def __add__(self, other):
if not isinstance(other, MinerData): if not isinstance(other, MinerData):
raise TypeError("Cannot add MinerData to non MinerData type.") raise TypeError("Cannot add MinerData to non MinerData type.")
cp = copy.deepcopy(self) cp = copy.deepcopy(self)
for key in self: for key in self.fields():
item = getattr(self, key) item = getattr(self, key)
other_item = getattr(other, key) other_item = getattr(other, key)
if item is None: if item is None:
@@ -221,34 +196,49 @@ class MinerData:
setattr(cp, key, item & other_item) setattr(cp, key, item & other_item)
return cp return cp
@computed_field # type: ignore[misc]
@property @property
def hashrate(self): # noqa - Skip PyCharm inspection def hashrate(self) -> AlgoHashRateType:
if len(self.hashboards) > 0: if len(self.hashboards) > 0:
hr_data = [] hr_data = []
for item in self.hashboards: for item in self.hashboards:
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, start=type(hr_data[0])(0)) return sum(hr_data, start=type(hr_data[0])(rate=0))
return self._hashrate return self.raw_hashrate
@field_serializer("hashrate")
def serialize_hashrate(self, hashrate: AlgoHashRateType | None) -> float:
if hashrate is not None:
return float(hashrate)
@field_serializer("expected_hashrate")
def serialize_expected_hashrate(
self, expected_hashrate: AlgoHashRateType | None, _info
) -> float:
if expected_hashrate is not None:
return float(expected_hashrate)
@hashrate.setter @hashrate.setter
def hashrate(self, val): def hashrate(self, val):
self._hashrate = val self.raw_hashrate = val
@computed_field # type: ignore[misc]
@property @property
def wattage_limit(self): # noqa - Skip PyCharm inspection def wattage_limit(self) -> int:
if self.config is not None: if self.config is not None:
if isinstance(self.config.mining_mode, MiningModePowerTune): if isinstance(self.config.mining_mode, MiningModePowerTune):
return self.config.mining_mode.power return self.config.mining_mode.power
return self._wattage_limit return self.raw_wattage_limit
@wattage_limit.setter @wattage_limit.setter
def wattage_limit(self, val: int): def wattage_limit(self, val: int):
self._wattage_limit = val self.raw_wattage_limit = val
@computed_field # type: ignore[misc]
@property @property
def total_chips(self): # noqa - Skip PyCharm inspection def total_chips(self) -> int | None:
if len(self.hashboards) > 0: if len(self.hashboards) > 0:
chip_data = [] chip_data = []
for item in self.hashboards: for item in self.hashboards:
@@ -258,34 +248,25 @@ class MinerData:
return sum(chip_data) return sum(chip_data)
return None return None
@total_chips.setter @computed_field # type: ignore[misc]
def total_chips(self, val):
pass
@property @property
def nominal(self): # noqa - Skip PyCharm inspection def nominal(self) -> bool | None:
if self.total_chips is None or self.expected_chips is None: if self.total_chips is None or self.expected_chips is None:
return None return None
return self.expected_chips == self.total_chips return self.expected_chips == self.total_chips
@nominal.setter @computed_field # type: ignore[misc]
def nominal(self, val):
pass
@property @property
def percent_expected_chips(self): # noqa - Skip PyCharm inspection def percent_expected_chips(self) -> int | None:
if self.total_chips is None or self.expected_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.expected_chips == 0: if self.total_chips == 0 or self.expected_chips == 0:
return 0 return 0
return round((self.total_chips / self.expected_chips) * 100) return round((self.total_chips / self.expected_chips) * 100)
@percent_expected_chips.setter @computed_field # type: ignore[misc]
def percent_expected_chips(self, val):
pass
@property @property
def percent_expected_hashrate(self): # noqa - Skip PyCharm inspection def percent_expected_hashrate(self) -> int | None:
if self.hashrate is None or self.expected_hashrate is None: if self.hashrate is None or self.expected_hashrate is None:
return None return None
try: try:
@@ -293,12 +274,9 @@ class MinerData:
except ZeroDivisionError: except ZeroDivisionError:
return 0 return 0
@percent_expected_hashrate.setter @computed_field # type: ignore[misc]
def percent_expected_hashrate(self, val):
pass
@property @property
def percent_expected_wattage(self): # noqa - Skip PyCharm inspection def percent_expected_wattage(self) -> int | None:
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
try: try:
@@ -306,12 +284,9 @@ class MinerData:
except ZeroDivisionError: except ZeroDivisionError:
return 0 return 0
@percent_expected_wattage.setter @computed_field # type: ignore[misc]
def percent_expected_wattage(self, val):
pass
@property @property
def temperature_avg(self): # noqa - Skip PyCharm inspection def temperature_avg(self) -> int | None:
total_temp = 0 total_temp = 0
temp_count = 0 temp_count = 0
for hb in self.hashboards: for hb in self.hashboards:
@@ -322,12 +297,9 @@ class MinerData:
return None return None
return round(total_temp / temp_count) return round(total_temp / temp_count)
@temperature_avg.setter @computed_field # type: ignore[misc]
def temperature_avg(self, val):
pass
@property @property
def efficiency(self): # noqa - Skip PyCharm inspection def efficiency(self) -> int | None:
if self.hashrate is None or self.wattage is None: if self.hashrate is None or self.wattage is None:
return None return None
try: try:
@@ -335,67 +307,45 @@ class MinerData:
except ZeroDivisionError: except ZeroDivisionError:
return 0 return 0
@efficiency.setter @computed_field # type: ignore[misc]
def efficiency(self, val):
pass
@property @property
def datetime(self): # noqa - Skip PyCharm inspection def datetime(self) -> str:
return self._datetime.isoformat() return self.raw_datetime.isoformat()
@datetime.setter
def datetime(self, val):
pass
@computed_field # type: ignore[misc]
@property @property
def timestamp(self): # noqa - Skip PyCharm inspection def timestamp(self) -> int:
return int(time.mktime(self._datetime.timetuple())) return int(time.mktime(self.raw_datetime.timetuple()))
@timestamp.setter
def timestamp(self, val):
pass
@computed_field # type: ignore[misc]
@property @property
def make(self): # noqa - Skip PyCharm inspection def make(self) -> str:
if self.device_info.make is not None: if self.device_info.make is not None:
return str(self.device_info.make) return str(self.device_info.make)
@make.setter @computed_field # type: ignore[misc]
def make(self, val):
pass
@property @property
def model(self): # noqa - Skip PyCharm inspection def model(self) -> str:
if self.device_info.model is not None: if self.device_info.model is not None:
return str(self.device_info.model) return str(self.device_info.model)
@model.setter @computed_field # type: ignore[misc]
def model(self, val):
pass
@property @property
def firmware(self): # noqa - Skip PyCharm inspection def firmware(self) -> str:
if self.device_info.firmware is not None: if self.device_info.firmware is not None:
return str(self.device_info.firmware) return str(self.device_info.firmware)
@firmware.setter @computed_field # type: ignore[misc]
def firmware(self, val):
pass
@property @property
def algo(self): # noqa - Skip PyCharm inspection def algo(self) -> str:
if self.device_info.algo is not None: if self.device_info.algo is not None:
return str(self.device_info.algo) return str(self.device_info.algo)
@algo.setter
def algo(self, val):
pass
def keys(self) -> list: def keys(self) -> list:
return [f.name for f in fields(self)] return list(self.model_fields.keys())
def asdict(self) -> dict: def asdict(self) -> dict:
return asdict(self, dict_factory=self.dict_factory) return self.model_dump()
def as_dict(self) -> dict: def as_dict(self) -> dict:
"""Get this dataclass as a dictionary. """Get this dataclass as a dictionary.
@@ -411,7 +361,7 @@ class MinerData:
Returns: Returns:
A JSON version of this class. A JSON version of this class.
""" """
return json.dumps(self.as_dict()) return self.model_dump_json()
def as_csv(self) -> str: def as_csv(self) -> str:
"""Get this dataclass as CSV. """Get this dataclass as CSV.
@@ -440,7 +390,7 @@ class MinerData:
field_data = [] field_data = []
tags = ["ip", "mac", "model", "hostname"] tags = ["ip", "mac", "model", "hostname"]
for attribute in self: for attribute in self.fields():
if attribute in tags: if attribute in tags:
escaped_data = self.get(attribute, "Unknown").replace(" ", "\\ ") escaped_data = self.get(attribute, "Unknown").replace(" ", "\\ ")
tag_data.append(f"{attribute}={escaped_data}") tag_data.append(f"{attribute}={escaped_data}")

View File

@@ -13,15 +13,16 @@
# 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 __future__ import annotations
from dataclasses import dataclass
from typing import Any from typing import Any
from .hashrate import AlgoHashRate from pydantic import BaseModel, field_serializer
from pyasic.device.algorithm.hashrate import AlgoHashRateType
@dataclass class HashBoard(BaseModel):
class HashBoard:
"""A Dataclass to standardize hashboard data. """A Dataclass to standardize hashboard data.
Attributes: Attributes:
@@ -39,16 +40,21 @@ class HashBoard:
""" """
slot: int = 0 slot: int = 0
hashrate: AlgoHashRate = None hashrate: AlgoHashRateType | None = None
temp: int = None temp: float | None = None
chip_temp: int = None chip_temp: float | None = None
chips: int = None chips: int | None = None
expected_chips: int = None expected_chips: int | None = None
serial_number: str = None serial_number: str | None = None
missing: bool = True missing: bool = True
tuned: bool = None tuned: bool | None = None
active: bool = None active: bool | None = None
voltage: float = None voltage: float | None = None
@field_serializer("hashrate")
def serialize_hashrate(self, hashrate: AlgoHashRateType | None) -> float:
if hashrate is not None:
return float(hashrate)
def get(self, __key: str, default: Any = None): def get(self, __key: str, default: Any = None):
try: try:

View File

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

View File

@@ -13,12 +13,10 @@
# 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.data.error_codes.base import BaseMinerError
from dataclasses import asdict, dataclass, fields
@dataclass class X19Error(BaseMinerError):
class X19Error:
"""A Dataclass to handle error codes of X19 miners. """A Dataclass to handle error codes of X19 miners.
Attributes: Attributes:
@@ -28,10 +26,3 @@ class X19Error:
error_message: str error_message: str
error_code: int = 0 error_code: int = 0
@classmethod
def fields(cls):
return fields(cls)
def asdict(self):
return asdict(self)

View File

@@ -0,0 +1,18 @@
from pydantic import BaseModel
class BaseMinerError(BaseModel):
@classmethod
def fields(cls):
return list(cls.model_fields.keys())
def asdict(self) -> dict:
return self.model_dump()
def as_dict(self) -> dict:
"""Get this dataclass as a dictionary.
Returns:
A dictionary version of this class.
"""
return self.asdict()

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,15 +0,0 @@
from enum import Enum
from pyasic.data.hashrate.sha256 import SHA256HashRate
from pyasic.device.algorithm.sha256 import SHA256Unit
class AlgoHashRate(Enum):
SHA256 = SHA256HashRate
def __call__(self, *args, **kwargs):
return self.value(*args, **kwargs)
class HashUnit:
SHA256 = SHA256Unit

View File

@@ -1,54 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass
from pyasic.device.algorithm import MinerAlgo
from pyasic.device.algorithm.sha256 import SHA256Unit
@dataclass
class SHA256HashRate:
rate: float
unit: SHA256Unit = MinerAlgo.SHA256.unit.default
def __float__(self):
return float(self.rate)
def __int__(self):
return int(self.rate)
def __repr__(self):
return f"{self.rate} {str(self.unit)}"
def __round__(self, n: int = None):
return round(self.rate, n)
def __add__(self, other: SHA256HashRate | int | float) -> SHA256HashRate:
if isinstance(other, SHA256HashRate):
return SHA256HashRate(self.rate + other.into(self.unit).rate, self.unit)
return SHA256HashRate(self.rate + other, self.unit)
def __sub__(self, other: SHA256HashRate | int | float) -> SHA256HashRate:
if isinstance(other, SHA256HashRate):
return SHA256HashRate(self.rate - other.into(self.unit).rate, self.unit)
return SHA256HashRate(self.rate - other, self.unit)
def __truediv__(self, other: SHA256HashRate | int | float):
if isinstance(other, SHA256HashRate):
return SHA256HashRate(self.rate / other.into(self.unit).rate, self.unit)
return SHA256HashRate(self.rate / other, self.unit)
def __floordiv__(self, other: SHA256HashRate | int | float):
if isinstance(other, SHA256HashRate):
return SHA256HashRate(self.rate // other.into(self.unit).rate, self.unit)
return SHA256HashRate(self.rate // other, self.unit)
def __mul__(self, other: SHA256HashRate | int | float):
if isinstance(other, SHA256HashRate):
return SHA256HashRate(self.rate * other.into(self.unit).rate, self.unit)
return SHA256HashRate(self.rate * other, self.unit)
def into(self, other: SHA256Unit) -> SHA256HashRate:
return SHA256HashRate(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,8 +1,10 @@
from dataclasses import dataclass, field
from enum import Enum from enum import Enum
from typing import Optional from typing import Optional
from urllib.parse import urlparse from urllib.parse import urlparse
from pydantic import BaseModel, computed_field, model_serializer
from typing_extensions import Self
class Scheme(Enum): class Scheme(Enum):
STRATUM_V1 = "stratum+tcp" STRATUM_V1 = "stratum+tcp"
@@ -10,13 +12,16 @@ class Scheme(Enum):
STRATUM_V1_SSL = "stratum+ssl" STRATUM_V1_SSL = "stratum+ssl"
@dataclass class PoolUrl(BaseModel):
class PoolUrl:
scheme: Scheme scheme: Scheme
host: str host: str
port: int port: int
pubkey: Optional[str] = None pubkey: Optional[str] = None
@model_serializer
def serialize(self):
return str(self)
def __str__(self) -> str: def __str__(self) -> str:
if self.scheme == Scheme.STRATUM_V2 and self.pubkey: if self.scheme == Scheme.STRATUM_V2 and self.pubkey:
return f"{self.scheme.value}://{self.host}:{self.port}/{self.pubkey}" return f"{self.scheme.value}://{self.host}:{self.port}/{self.pubkey}"
@@ -24,8 +29,10 @@ class PoolUrl:
return f"{self.scheme.value}://{self.host}:{self.port}" return f"{self.scheme.value}://{self.host}:{self.port}"
@classmethod @classmethod
def from_str(cls, url: str) -> "PoolUrl": def from_str(cls, url: str) -> Self | None:
parsed_url = urlparse(url) parsed_url = urlparse(url)
if not parsed_url.hostname:
return None
if not parsed_url.scheme.strip() == "": if not parsed_url.scheme.strip() == "":
scheme = Scheme(parsed_url.scheme) scheme = Scheme(parsed_url.scheme)
else: else:
@@ -36,8 +43,7 @@ class PoolUrl:
return cls(scheme=scheme, host=host, port=port, pubkey=pubkey) return cls(scheme=scheme, host=host, port=port, pubkey=pubkey)
@dataclass class PoolMetrics(BaseModel):
class PoolMetrics:
"""A dataclass to standardize pool metrics returned from miners. """A dataclass to standardize pool metrics returned from miners.
Attributes: Attributes:
@@ -54,27 +60,23 @@ class PoolMetrics:
pool_stale_percent: Percentage of stale shares by the pool. pool_stale_percent: Percentage of stale shares by the pool.
""" """
url: PoolUrl url: PoolUrl | None
accepted: int = None accepted: int | None = None
rejected: int = None rejected: int | None = None
get_failures: int = None get_failures: int | None = None
remote_failures: int = None remote_failures: int | None = None
active: bool = None active: bool | None = None
alive: bool = None alive: bool | None = None
index: int = None index: int | None = None
user: str = None user: str | None = None
pool_rejected_percent: float = field(init=False)
pool_stale_percent: float = field(init=False)
@computed_field # type: ignore[misc]
@property @property
def pool_rejected_percent(self) -> float: # noqa - Skip PyCharm inspection def pool_rejected_percent(self) -> float: # noqa - Skip PyCharm inspection
"""Calculate and return the percentage of rejected shares""" """Calculate and return the percentage of rejected shares"""
return self._calculate_percentage(self.rejected, self.accepted + self.rejected) return self._calculate_percentage(self.rejected, self.accepted + self.rejected)
@pool_rejected_percent.setter @computed_field # type: ignore[misc]
def pool_rejected_percent(self, val):
pass
@property @property
def pool_stale_percent(self) -> float: # noqa - Skip PyCharm inspection def pool_stale_percent(self) -> float: # noqa - Skip PyCharm inspection
"""Calculate and return the percentage of stale shares.""" """Calculate and return the percentage of stale shares."""
@@ -82,10 +84,6 @@ class PoolMetrics:
self.get_failures, self.accepted + self.rejected self.get_failures, self.accepted + self.rejected
) )
@pool_stale_percent.setter
def pool_stale_percent(self, val):
pass
@staticmethod @staticmethod
def _calculate_percentage(value: int, total: int) -> float: def _calculate_percentage(value: int, total: int) -> float:
"""Calculate the percentage.""" """Calculate the percentage."""

View File

@@ -1,5 +1,26 @@
from pyasic.device.algorithm.sha256 import SHA256Algo from .base import MinerAlgoType
from .blake256 import Blake256Algo
from .eaglesong import EaglesongAlgo
from .equihash import EquihashAlgo
from .ethash import EtHashAlgo
from .handshake import HandshakeAlgo
from .hashrate import *
from .hashrate.unit import *
from .kadena import KadenaAlgo
from .kheavyhash import KHeavyHashAlgo
from .scrypt import ScryptAlgo
from .sha256 import SHA256Algo
from .x11 import X11Algo
class MinerAlgo: class MinerAlgo:
SHA256 = SHA256Algo SHA256 = SHA256Algo
SCRYPT = ScryptAlgo
KHEAVYHASH = KHeavyHashAlgo
KADENA = KadenaAlgo
HANDSHAKE = HandshakeAlgo
X11 = X11Algo
BLAKE256 = Blake256Algo
EAGLESONG = EaglesongAlgo
ETHASH = EtHashAlgo
EQUIHASH = EquihashAlgo

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from .base import AlgoHashRateUnitType
class Blake256Unit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = TH

View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from .base import AlgoHashRateUnitType
class EaglesongUnit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = TH

View File

@@ -0,0 +1,34 @@
from __future__ import annotations
from .base import AlgoHashRateUnitType
class EquihashUnit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = KH
def __str__(self):
if self.value == self.H:
return "Sol/s"
if self.value == self.KH:
return "KSol/s"
if self.value == self.MH:
return "MSol/s"
if self.value == self.GH:
return "GSol/s"
if self.value == self.TH:
return "TSol/s"
if self.value == self.PH:
return "PSol/s"
if self.value == self.EH:
return "ESol/s"
if self.value == self.ZH:
return "ZSol/s"

View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from .base import AlgoHashRateUnitType
class EtHashUnit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = MH

View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from .base import AlgoHashRateUnitType
class HandshakeUnit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = TH

View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from .base import AlgoHashRateUnitType
class KadenaUnit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = TH

View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from .base import AlgoHashRateUnitType
class KHeavyHashUnit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = TH

View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from .base import AlgoHashRateUnitType
class ScryptUnit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = GH

View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from .base import AlgoHashRateUnitType
class SHA256Unit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = TH

View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from .base import AlgoHashRateUnitType
class X11Unit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = GH

View File

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

View File

@@ -0,0 +1,13 @@
from __future__ import annotations
from .base import MinerAlgoType
from .hashrate import KadenaHashRate
from .hashrate.unit import KadenaUnit
# make this json serializable
class KadenaAlgo(MinerAlgoType):
hashrate: type[KadenaHashRate] = KadenaHashRate
unit: type[KadenaUnit] = KadenaUnit
name = "Kadena"

View File

@@ -0,0 +1,13 @@
from __future__ import annotations
from .base import MinerAlgoType
from .hashrate import KHeavyHashHashRate
from .hashrate.unit import KHeavyHashUnit
# make this json serializable
class KHeavyHashAlgo(MinerAlgoType):
hashrate: type[KHeavyHashHashRate] = KHeavyHashHashRate
unit: type[KHeavyHashUnit] = KHeavyHashUnit
name = "KHeavyHash"

View File

@@ -0,0 +1,12 @@
from __future__ import annotations
from .base import MinerAlgoType
from .hashrate import ScryptHashRate
from .hashrate.unit import ScryptUnit
class ScryptAlgo(MinerAlgoType):
hashrate: type[ScryptHashRate] = ScryptHashRate
unit: type[ScryptUnit] = ScryptUnit
name = "Scrypt"

View File

@@ -1,68 +1,13 @@
from __future__ import annotations from __future__ import annotations
from enum import IntEnum from .base import MinerAlgoType
from .hashrate import SHA256HashRate
from .hashrate.unit import SHA256Unit
class SHA256Unit(IntEnum):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = TH
def __str__(self):
if self.value == self.H:
return "H/s"
if self.value == self.KH:
return "KH/s"
if self.value == self.MH:
return "MH/s"
if self.value == self.GH:
return "GH/s"
if self.value == self.TH:
return "TH/s"
if self.value == self.PH:
return "PH/s"
if self.value == self.EH:
return "EH/s"
if self.value == self.ZH:
return "ZH/s"
@classmethod
def from_str(cls, value: str):
if value == "H":
return cls.H
elif value == "KH":
return cls.KH
elif value == "MH":
return cls.MH
elif value == "GH":
return cls.GH
elif value == "TH":
return cls.TH
elif value == "PH":
return cls.PH
elif value == "EH":
return cls.EH
elif value == "ZH":
return cls.ZH
return cls.default
def __repr__(self):
return str(self)
# make this json serializable # make this json serializable
class _SHA256Algo(str): class SHA256Algo(MinerAlgoType):
unit = SHA256Unit hashrate: type[SHA256HashRate] = SHA256HashRate
unit: type[SHA256Unit] = SHA256Unit
def __repr__(self): name = "SHA256"
return "SHA256Algo"
SHA256Algo = _SHA256Algo("SHA256")

View File

@@ -0,0 +1,13 @@
from __future__ import annotations
from .base import MinerAlgoType
from .hashrate import X11HashRate
from .hashrate.unit import X11Unit
# make this json serializable
class X11Algo(MinerAlgoType):
hashrate: type[X11HashRate] = X11HashRate
unit: type[X11Unit] = X11Unit
name = "X11"

View File

@@ -1,7 +1,11 @@
from enum import Enum from enum import Enum
class AntminerModels(str, Enum): class MinerModelType(str, Enum):
pass
class AntminerModels(MinerModelType):
D3 = "D3" D3 = "D3"
HS3 = "HS3" HS3 = "HS3"
L3Plus = "L3+" L3Plus = "L3+"
@@ -11,12 +15,14 @@ class AntminerModels(str, Enum):
KS5 = "KS5" KS5 = "KS5"
L7 = "L7" L7 = "L7"
K7 = "K7" K7 = "K7"
D7 = "D7"
E9Pro = "E9Pro" E9Pro = "E9Pro"
S9 = "S9" S9 = "S9"
S9i = "S9i" S9i = "S9i"
S9j = "S9j" S9j = "S9j"
T9 = "T9" T9 = "T9"
D9 = "D9" D9 = "D9"
L9 = "L9"
Z15 = "Z15" Z15 = "Z15"
Z15Pro = "Z15 Pro" Z15Pro = "Z15 Pro"
S17 = "S17" S17 = "S17"
@@ -57,7 +63,7 @@ class AntminerModels(str, Enum):
return self.value return self.value
class WhatsminerModels(str, Enum): class WhatsminerModels(MinerModelType):
M20V10 = "M20 V10" M20V10 = "M20 V10"
M20SV10 = "M20S V10" M20SV10 = "M20S V10"
M20SV20 = "M20S V20" M20SV20 = "M20S V20"
@@ -278,7 +284,7 @@ class WhatsminerModels(str, Enum):
return self.value return self.value
class AvalonminerModels(str, Enum): class AvalonminerModels(MinerModelType):
Avalon721 = "Avalon 721" Avalon721 = "Avalon 721"
Avalon741 = "Avalon 741" Avalon741 = "Avalon 741"
Avalon761 = "Avalon 761" Avalon761 = "Avalon 761"
@@ -290,6 +296,7 @@ class AvalonminerModels(str, Enum):
Avalon1047 = "Avalon 1047" Avalon1047 = "Avalon 1047"
Avalon1066 = "Avalon 1066" Avalon1066 = "Avalon 1066"
Avalon1166Pro = "Avalon 1166 Pro" Avalon1166Pro = "Avalon 1166 Pro"
Avalon1126Pro = "Avalon 1126 Pro"
Avalon1246 = "Avalon 1246" Avalon1246 = "Avalon 1246"
AvalonNano3 = "Avalon Nano 3" AvalonNano3 = "Avalon Nano 3"
@@ -297,7 +304,7 @@ class AvalonminerModels(str, Enum):
return self.value return self.value
class InnosiliconModels(str, Enum): class InnosiliconModels(MinerModelType):
T3HPlus = "T3H+" T3HPlus = "T3H+"
A10X = "A10X" A10X = "A10X"
A11 = "A11" A11 = "A11"
@@ -307,7 +314,7 @@ class InnosiliconModels(str, Enum):
return self.value return self.value
class GoldshellModels(str, Enum): class GoldshellModels(MinerModelType):
CK5 = "CK5" CK5 = "CK5"
HS5 = "HS5" HS5 = "HS5"
KD5 = "KD5" KD5 = "KD5"
@@ -319,7 +326,7 @@ class GoldshellModels(str, Enum):
return self.value return self.value
class ePICModels(str, Enum): class ePICModels(MinerModelType):
BM520i = "BlockMiner 520i" BM520i = "BlockMiner 520i"
BM720i = "BlockMiner 720i" BM720i = "BlockMiner 720i"
@@ -327,7 +334,7 @@ class ePICModels(str, Enum):
return self.value return self.value
class AuradineModels(str, Enum): class AuradineModels(MinerModelType):
AT1500 = "AT1500" AT1500 = "AT1500"
AT2860 = "AT2860" AT2860 = "AT2860"
AT2880 = "AT2880" AT2880 = "AT2880"
@@ -340,16 +347,17 @@ class AuradineModels(str, Enum):
return self.value return self.value
class BitAxeModels(str, Enum): class BitAxeModels(MinerModelType):
BM1366 = "Ultra" BM1366 = "Ultra"
BM1368 = "Supra" BM1368 = "Supra"
BM1397 = "Max" BM1397 = "Max"
BM1370 = "Gamma"
def __str__(self): def __str__(self):
return self.value return self.value
class IceRiverModels(str, Enum): class IceRiverModels(MinerModelType):
KS0 = "KS0" KS0 = "KS0"
KS1 = "KS1" KS1 = "KS1"
KS2 = "KS2" KS2 = "KS2"
@@ -364,7 +372,7 @@ class IceRiverModels(str, Enum):
return self.value return self.value
class HammerModels(str, Enum): class HammerModels(MinerModelType):
D10 = "D10" D10 = "D10"
def __str__(self): def __str__(self):

View File

@@ -0,0 +1,21 @@
# ------------------------------------------------------------------------------
# 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 AntminerModern
from pyasic.miners.device.models import D7
class BMMinerD7(AntminerModern, D7):
pass

View File

@@ -13,5 +13,6 @@
# 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 .D7 import BMMinerD7
from .K7 import BMMinerK7 from .K7 import BMMinerK7
from .L7 import BMMinerL7 from .L7 import BMMinerL7

View File

@@ -0,0 +1,22 @@
# ------------------------------------------------------------------------------
# Copyright 2022 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerModern
from pyasic.miners.device.models import L9
class BMMinerL9(AntminerModern, L9):
pass

View File

@@ -16,5 +16,6 @@
from .D9 import BMMinerD9 from .D9 import BMMinerD9
from .E9 import BMMinerE9Pro from .E9 import BMMinerE9Pro
from .L9 import BMMinerL9
from .S9 import BMMinerS9, BMMinerS9i, BMMinerS9j from .S9 import BMMinerS9, BMMinerS9i, BMMinerS9j
from .T9 import BMMinerT9 from .T9 import BMMinerT9

View File

@@ -0,0 +1,99 @@
# ------------------------------------------------------------------------------
# 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 HiveonModern
from pyasic.miners.device.models import (
S19,
S19L,
S19XP,
S19a,
S19aPro,
S19Hydro,
S19i,
S19j,
S19jNoPIC,
S19jPro,
S19KPro,
S19Plus,
S19Pro,
S19ProHydro,
S19ProPlus,
S19ProPlusHydro,
)
class HiveonS19(HiveonModern, S19):
pass
class HiveonS19Plus(HiveonModern, S19Plus):
pass
class HiveonS19i(HiveonModern, S19i):
pass
class HiveonS19Pro(HiveonModern, S19Pro):
pass
class HiveonS19ProPlus(HiveonModern, S19ProPlus):
pass
class HiveonS19XP(HiveonModern, S19XP):
pass
class HiveonS19a(HiveonModern, S19a):
pass
class HiveonS19aPro(HiveonModern, S19aPro):
pass
class HiveonS19j(HiveonModern, S19j):
pass
class HiveonS19jNoPIC(HiveonModern, S19jNoPIC):
pass
class HiveonS19jPro(HiveonModern, S19jPro):
pass
class HiveonS19L(HiveonModern, S19L):
pass
class HiveonS19ProHydro(HiveonModern, S19ProHydro):
pass
class HiveonS19Hydro(HiveonModern, S19Hydro):
pass
class HiveonS19ProPlusHydro(HiveonModern, S19ProPlusHydro):
pass
class HiveonS19KPro(HiveonModern, S19KPro):
pass

View File

@@ -0,0 +1,22 @@
# ------------------------------------------------------------------------------
# Copyright 2022 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import HiveonModern
from pyasic.miners.device.models import T19
class HiveonT19(HiveonModern, T19):
pass

View 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 .S19 import (
HiveonS19,
HiveonS19a,
HiveonS19aPro,
HiveonS19Hydro,
HiveonS19i,
HiveonS19j,
HiveonS19jNoPIC,
HiveonS19jPro,
HiveonS19KPro,
HiveonS19L,
HiveonS19Plus,
HiveonS19Pro,
HiveonS19ProHydro,
HiveonS19ProPlus,
HiveonS19ProPlusHydro,
HiveonS19XP,
)
from .T19 import HiveonT19

View File

@@ -18,9 +18,10 @@ from typing import List, Optional
import asyncssh import asyncssh
from pyasic.data import AlgoHashRate, HashBoard, HashUnit from pyasic.data import HashBoard
from pyasic.device.algorithm import AlgoHashRate, HashUnit
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends import Hiveon from pyasic.miners.backends import HiveonOld
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.models import T9 from pyasic.miners.device.models import T9
@@ -62,28 +63,21 @@ HIVEON_T9_DATA_LOC = DataLocations(
"_get_uptime", "_get_uptime",
[RPCAPICommand("rpc_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_pools", "pools")],
),
} }
) )
class HiveonT9(Hiveon, T9): class HiveonT9(HiveonOld, T9):
data_locations = HIVEON_T9_DATA_LOC data_locations = HIVEON_T9_DATA_LOC
################################################## ##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def get_mac(self):
try:
mac = (
(await self.send_ssh_command("cat /sys/class/net/eth0/address"))
.strip()
.upper()
)
return mac
except (TypeError, ValueError, asyncssh.Error, OSError, AttributeError):
pass
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
hashboards = [ hashboards = [
HashBoard(slot=board, expected_chips=self.expected_chips) HashBoard(slot=board, expected_chips=self.expected_chips)
@@ -122,29 +116,12 @@ class HiveonT9(Hiveon, T9):
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
hashboards[board].hashrate = AlgoHashRate.SHA256( hashboards[board].hashrate = AlgoHashRate.SHA256(
hashrate, HashUnit.SHA256.GH rate=float(hashrate), unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[board].chips = chips hashboards[board].chips = chips
return hashboards return hashboards
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
if not rpc_stats:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_stats:
boards = rpc_stats.get("STATS")
try:
wattage_raw = boards[1]["chain_power"]
except (KeyError, IndexError):
pass
else:
# parse wattage position out of raw data
return round(float(wattage_raw.split(" ")[0]))
async def _get_env_temp(self, rpc_stats: dict = None) -> Optional[float]: async def _get_env_temp(self, rpc_stats: dict = None) -> Optional[float]:
env_temp_list = [] env_temp_list = []
board_map = { board_map = {
@@ -168,4 +145,4 @@ class HiveonT9(Hiveon, T9):
pass pass
if not env_temp_list == []: if not env_temp_list == []:
return round(float(sum(env_temp_list) / len(env_temp_list)), 2) return round(sum(env_temp_list) / len(env_temp_list))

View File

@@ -15,3 +15,4 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .X9 import * from .X9 import *
from .X19 import *

View File

@@ -22,6 +22,7 @@ from pyasic.miners.device.models import (
S19aPro, S19aPro,
S19j, S19j,
S19jPro, S19jPro,
S19kPro,
S19NoPIC, S19NoPIC,
S19Pro, S19Pro,
S19ProHydro, S19ProHydro,
@@ -62,3 +63,7 @@ class VNishS19jPro(VNish, S19jPro):
class VNishS19ProHydro(VNish, S19ProHydro): class VNishS19ProHydro(VNish, S19ProHydro):
pass pass
class VNishS19kPro(VNish, S19kPro):
pass

View File

@@ -20,6 +20,7 @@ from .S19 import (
VNishS19aPro, VNishS19aPro,
VNishS19j, VNishS19j,
VNishS19jPro, VNishS19jPro,
VNishS19kPro,
VNishS19NoPIC, VNishS19NoPIC,
VNishS19Pro, VNishS19Pro,
VNishS19ProHydro, VNishS19ProHydro,

View File

@@ -0,0 +1,22 @@
# ------------------------------------------------------------------------------
# Copyright 2022 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.device.models import Avalon1126Pro
class CGMinerAvalon1126Pro(AvalonMiner, Avalon1126Pro):
pass

View File

@@ -14,4 +14,5 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .A1126 import CGMinerAvalon1126Pro
from .A1166 import CGMinerAvalon1166Pro from .A1166 import CGMinerAvalon1166Pro

View File

@@ -25,7 +25,7 @@ from .cgminer import CGMiner
from .epic import ePIC from .epic import ePIC
from .goldshell import GoldshellMiner from .goldshell import GoldshellMiner
from .hammer import BlackMiner from .hammer import BlackMiner
from .hiveon import Hiveon from .hiveon import HiveonModern, HiveonOld
from .iceriver import IceRiver from .iceriver import IceRiver
from .innosilicon import Innosilicon from .innosilicon import Innosilicon
from .luxminer import LUXMiner from .luxminer import LUXMiner

View File

@@ -16,12 +16,13 @@
import logging import logging
from pathlib import Path from pathlib import Path
from typing import List, Optional, Union from typing import List, Optional
from pyasic.config import MinerConfig, MiningModeConfig from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.data.pools import PoolMetrics, PoolUrl from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.device.algorithm import AlgoHashRate
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.backends.bmminer import BMMiner
from pyasic.miners.backends.cgminer import CGMiner from pyasic.miners.backends.cgminer import CGMiner
@@ -183,13 +184,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 = MiningModeConfig.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 = MiningModeConfig.normal cfg.miner_mode = MiningModeConfig.normal()
await self.send_config(cfg) await self.send_config(cfg)
return True return True
@@ -239,7 +240,7 @@ class AntminerModern(BMMiner):
for item in web_summary["SUMMARY"][0]["status"]: for item in web_summary["SUMMARY"][0]["status"]:
try: try:
if not item["status"] == "s": if not item["status"] == "s":
errors.append(X19Error(item["msg"])) errors.append(X19Error(error_message=item["msg"]))
except KeyError: except KeyError:
continue continue
except LookupError: except LookupError:
@@ -248,7 +249,7 @@ class AntminerModern(BMMiner):
async def _get_hashboards(self) -> List[HashBoard]: async def _get_hashboards(self) -> List[HashBoard]:
hashboards = [ hashboards = [
HashBoard(idx, expected_chips=self.expected_chips) HashBoard(slot=idx, expected_chips=self.expected_chips)
for idx in range(self.expected_hashboards) for idx in range(self.expected_hashboards)
] ]
@@ -260,8 +261,8 @@ class AntminerModern(BMMiner):
if rpc_stats is not None: if rpc_stats is not None:
try: try:
for board in rpc_stats["STATS"][0]["chain"]: for board in rpc_stats["STATS"][0]["chain"]:
hashboards[board["index"]].hashrate = AlgoHashRate.SHA256( hashboards[board["index"]].hashrate = self.algo.hashrate(
board["rate_real"], HashUnit.SHA256.GH rate=board["rate_real"], unit=self.algo.unit.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[board["index"]].chips = board["asic_num"] hashboards[board["index"]].chips = board["asic_num"]
board_temp_data = list( board_temp_data = list(
@@ -317,8 +318,8 @@ class AntminerModern(BMMiner):
rate_unit = rpc_stats["STATS"][1]["rate_unit"] rate_unit = rpc_stats["STATS"][1]["rate_unit"]
except KeyError: except KeyError:
rate_unit = "GH" rate_unit = "GH"
return AlgoHashRate.SHA256( return self.algo.hashrate(
expected_rate, HashUnit.SHA256.from_str(rate_unit) rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass
@@ -597,44 +598,52 @@ class AntminerOld(CGMiner):
pass pass
if rpc_stats is not None: if rpc_stats is not None:
board_offset = -1 try:
boards = rpc_stats["STATS"] board_offset = -1
if len(boards) > 1: boards = rpc_stats["STATS"]
for board_num in range(1, 16, 5): if len(boards) > 1:
for _b_num in range(5): for board_num in range(1, 16, 5):
b = boards[1].get(f"chain_acn{board_num + _b_num}") for _b_num in range(5):
b = boards[1].get(f"chain_acn{board_num + _b_num}")
if b and not b == 0 and board_offset == -1: if b and not b == 0 and board_offset == -1:
board_offset = board_num board_offset = board_num
if board_offset == -1: if board_offset == -1:
board_offset = 1 board_offset = 1
for i in range(board_offset, board_offset + self.expected_hashboards): for i in range(
hashboard = HashBoard( board_offset, board_offset + self.expected_hashboards
slot=i - board_offset, expected_chips=self.expected_chips ):
) hashboard = HashBoard(
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}")
if chip_temp: if chip_temp:
hashboard.chip_temp = round(chip_temp) hashboard.chip_temp = round(chip_temp)
temp = boards[1].get(f"temp2_{i}") temp = boards[1].get(f"temp2_{i}")
if temp: if temp:
hashboard.temp = round(temp) hashboard.temp = round(temp)
hashrate = boards[1].get(f"chain_rate{i}") hashrate = boards[1].get(f"chain_rate{i}")
if hashrate: if hashrate:
hashboard.hashrate = AlgoHashRate.SHA256( hashboard.hashrate = self.algo.hashrate(
float(hashrate), HashUnit.SHA256.GH rate=float(hashrate), unit=self.algo.unit.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
chips = boards[1].get(f"chain_acn{i}") chips = boards[1].get(f"chain_acn{i}")
if chips: if chips:
hashboard.chips = chips hashboard.chips = chips
hashboard.missing = False hashboard.missing = False
if (not chips) or (not chips > 0): if (not chips) or (not chips > 0):
hashboard.missing = True hashboard.missing = True
hashboards.append(hashboard) hashboards.append(hashboard)
except LookupError:
return [
HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards)
]
return hashboards return hashboards

View File

@@ -18,7 +18,8 @@ from enum import Enum
from typing import List, Optional from typing import List, Optional
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.data import Fan, HashBoard
from pyasic.device.algorithm import AlgoHashRate
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.data import ( from pyasic.miners.data import (
DataFunction, DataFunction,
@@ -292,8 +293,9 @@ class Auradine(StockFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return self.algo.hashrate(
rpc_summary["SUMMARY"][0]["MHS 5s"], HashUnit.SHA256.MH rate=float(rpc_summary["SUMMARY"][0]["MHS 5s"]),
unit=self.algo.unit.MH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -321,10 +323,10 @@ class Auradine(StockFirmware):
try: try:
for board in rpc_devs["DEVS"]: for board in rpc_devs["DEVS"]:
b_id = board["ID"] - 1 b_id = board["ID"] - 1
hashboards[b_id].hashrate = AlgoHashRate.SHA256( hashboards[b_id].hashrate = self.algo.hashrate(
board["MHS 5s"], HashUnit.SHA256.MH rate=float(board["MHS 5s"]), unit=self.algo.unit.MH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[b_id].temp = round(float(float(board["Temperature"])), 2) hashboards[b_id].temp = round(float(board["Temperature"]))
hashboards[b_id].missing = False hashboards[b_id].missing = False
except LookupError: except LookupError:
pass pass
@@ -390,7 +392,7 @@ class Auradine(StockFirmware):
if web_fan is not None: if web_fan is not None:
try: try:
for fan in web_fan["Fan"]: for fan in web_fan["Fan"]:
fans.append(Fan(round(fan["Speed"]))) fans.append(Fan(speed=round(fan["Speed"])))
except LookupError: except LookupError:
pass pass
return fans return fans

View File

@@ -17,7 +17,8 @@
import re import re
from typing import List, Optional from typing import List, Optional
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.data import Fan, HashBoard
from pyasic.device.algorithm import AlgoHashRate
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends.cgminer import CGMiner from pyasic.miners.backends.cgminer import CGMiner
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
@@ -183,8 +184,8 @@ class AvalonMiner(CGMiner):
if rpc_devs is not None: if rpc_devs is not None:
try: try:
return AlgoHashRate.SHA256( return self.algo.hashrate(
rpc_devs["DEVS"][0]["MHS 1m"], HashUnit.SHA256.MH rate=float(rpc_devs["DEVS"][0]["MHS 1m"]), unit=self.algo.unit.MH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
pass pass
@@ -216,8 +217,8 @@ class AvalonMiner(CGMiner):
try: try:
board_hr = parsed_stats["MGHS"][board] board_hr = parsed_stats["MGHS"][board]
hashboards[board].hashrate = AlgoHashRate.SHA256( hashboards[board].hashrate = self.algo.hashrate(
float(board_hr), HashUnit.SHA256.GH rate=float(board_hr), unit=self.algo.unit.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass
@@ -252,8 +253,8 @@ class AvalonMiner(CGMiner):
try: try:
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"] unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
return AlgoHashRate.SHA256( return self.algo.hashrate(
float(parsed_stats["GHSmm"][0]), HashUnit.SHA256.GH rate=float(parsed_stats["GHSmm"][0]), unit=self.algo.unit.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
pass pass

View File

@@ -17,8 +17,9 @@
from typing import List, Optional from typing import List, Optional
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.data import Fan, HashBoard
from pyasic.data.pools import PoolMetrics, PoolUrl from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.device.algorithm import AlgoHashRate
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.firmware import StockFirmware from pyasic.miners.device.firmware import StockFirmware
@@ -120,8 +121,9 @@ class BFGMiner(StockFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return self.algo.hashrate(
rpc_summary["SUMMARY"][0]["MHS 20s"], HashUnit.SHA256.MH rate=float(rpc_summary["SUMMARY"][0]["MHS 20s"]),
unit=self.algo.unit.MH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -166,8 +168,8 @@ class BFGMiner(StockFirmware):
hashrate = boards[1].get(f"chain_rate{i}") hashrate = boards[1].get(f"chain_rate{i}")
if hashrate: if hashrate:
hashboard.hashrate = AlgoHashRate.SHA256( hashboard.hashrate = self.algo.hashrate(
hashrate, HashUnit.SHA256.GH rate=float(hashrate), unit=self.algo.unit.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
chips = boards[1].get(f"chain_acn{i}") chips = boards[1].get(f"chain_acn{i}")
@@ -259,8 +261,8 @@ class BFGMiner(StockFirmware):
rate_unit = rpc_stats["STATS"][1]["rate_unit"] rate_unit = rpc_stats["STATS"][1]["rate_unit"]
except KeyError: except KeyError:
rate_unit = "GH" rate_unit = "GH"
return AlgoHashRate.SHA256( return self.algo.hashrate(
expected_rate, HashUnit.SHA256.from_str(rate_unit) rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -1,8 +1,9 @@
from typing import List, Optional from typing import List, Optional
from pyasic import APIError, MinerConfig from pyasic import APIError, MinerConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.data import Fan, HashBoard
from pyasic.device import MinerFirmware from pyasic.device.algorithm import AlgoHashRate
from pyasic.device.firmware import MinerFirmware
from pyasic.miners.base import BaseMiner from pyasic.miners.base import BaseMiner
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
from pyasic.web.bitaxe import BitAxeWebAPI from pyasic.web.bitaxe import BitAxeWebAPI
@@ -93,8 +94,8 @@ class BitAxe(BaseMiner):
if web_system_info is not None: if web_system_info is not None:
try: try:
return AlgoHashRate.SHA256( return self.algo.hashrate(
web_system_info["hashRate"], HashUnit.SHA256.GH rate=float(web_system_info["hashRate"]), unit=self.algo.unit.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except KeyError: except KeyError:
pass pass
@@ -123,8 +124,9 @@ class BitAxe(BaseMiner):
try: try:
return [ return [
HashBoard( HashBoard(
hashrate=AlgoHashRate.SHA256( hashrate=self.algo.hashrate(
web_system_info["hashRate"], HashUnit.SHA256.GH rate=float(web_system_info["hashRate"]),
unit=self.algo.unit.GH,
).into(self.algo.unit.default), ).into(self.algo.unit.default),
chip_temp=web_system_info.get("temp"), chip_temp=web_system_info.get("temp"),
temp=web_system_info.get("vrTemp"), temp=web_system_info.get("vrTemp"),

View File

@@ -17,8 +17,9 @@
from typing import List, Optional from typing import List, Optional
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.data import Fan, HashBoard
from pyasic.data.pools import PoolMetrics, PoolUrl from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.device.algorithm import AlgoHashRate
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.firmware import StockFirmware from pyasic.miners.device.firmware import StockFirmware
@@ -124,8 +125,9 @@ class BMMiner(StockFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return self.algo.hashrate(
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
unit=self.algo.unit.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -183,8 +185,8 @@ class BMMiner(StockFirmware):
hashrate = boards[1].get(f"chain_rate{i}") hashrate = boards[1].get(f"chain_rate{i}")
if hashrate: if hashrate:
hashboard.hashrate = AlgoHashRate.SHA256( hashboard.hashrate = self.algo.hashrate(
hashrate, HashUnit.SHA256.GH rate=float(hashrate), unit=self.algo.unit.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
chips = boards[1].get(f"chain_acn{i}") chips = boards[1].get(f"chain_acn{i}")
@@ -245,8 +247,8 @@ class BMMiner(StockFirmware):
rate_unit = rpc_stats["STATS"][1]["rate_unit"] rate_unit = rpc_stats["STATS"][1]["rate_unit"]
except KeyError: except KeyError:
rate_unit = "GH" rate_unit = "GH"
return AlgoHashRate.SHA256( return self.algo.hashrate(
expected_rate, HashUnit.SHA256.from_str(rate_unit) rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -29,9 +29,10 @@ except ImportError:
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.config.mining import MiningModePowerTune from pyasic.config.mining import MiningModePowerTune
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
from pyasic.data.pools import PoolMetrics, PoolUrl from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.device.algorithm import AlgoHashRate, AlgoHashRateType
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.data import ( from pyasic.miners.data import (
DataFunction, DataFunction,
@@ -222,7 +223,7 @@ class BOSMiner(BraiinsOSFirmware):
cfg = await self.get_config() cfg = await self.get_config()
if cfg is None: if cfg is None:
return False return False
cfg.mining_mode = MiningModePowerTune(wattage) cfg.mining_mode = MiningModePowerTune(power=wattage)
await self.send_config(cfg) await self.send_config(cfg)
except APIError: except APIError:
raise raise
@@ -362,8 +363,9 @@ class BOSMiner(BraiinsOSFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return self.algo.hashrate(
rpc_summary["SUMMARY"][0]["MHS 1m"], HashUnit.SHA256.MH rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
unit=self.algo.unit.MH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
pass pass
@@ -434,8 +436,8 @@ class BOSMiner(BraiinsOSFirmware):
for board in rpc_devs["DEVS"]: for board in rpc_devs["DEVS"]:
_id = board["ID"] - offset _id = board["ID"] - offset
hashboards[_id].hashrate = AlgoHashRate.SHA256( hashboards[_id].hashrate = self.algo.hashrate(
board["MHS 1m"], HashUnit.SHA256.MH rate=float(board["MHS 1m"]), unit=self.algo.unit.MH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
@@ -481,7 +483,7 @@ class BOSMiner(BraiinsOSFirmware):
fans = [] fans = []
for n in range(self.expected_fans): for n in range(self.expected_fans):
try: try:
fans.append(Fan(rpc_fans["FANS"][n]["RPM"])) fans.append(Fan(speed=rpc_fans["FANS"][n]["RPM"]))
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
return fans return fans
@@ -512,7 +514,9 @@ class BOSMiner(BraiinsOSFirmware):
]: ]:
_error = board["Status"].split(" {")[0] _error = board["Status"].split(" {")[0]
_error = _error[0].lower() + _error[1:] _error = _error[0].lower() + _error[1:]
errors.append(BraiinsOSError(f"Slot {_id} {_error}")) errors.append(
BraiinsOSError(error_message=f"Slot {_id} {_error}")
)
return errors return errors
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
@@ -531,7 +535,7 @@ class BOSMiner(BraiinsOSFirmware):
async def _get_expected_hashrate( async def _get_expected_hashrate(
self, rpc_devs: dict = None self, rpc_devs: dict = None
) -> Optional[AlgoHashRate]: ) -> Optional[AlgoHashRateType]:
if rpc_devs is None: if rpc_devs is None:
try: try:
rpc_devs = await self.rpc.devs() rpc_devs = await self.rpc.devs()
@@ -543,16 +547,21 @@ class BOSMiner(BraiinsOSFirmware):
hr_list = [] hr_list = []
for board in rpc_devs["DEVS"]: for board in rpc_devs["DEVS"]:
expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2) expected_hashrate = float(board["Nominal MHS"])
if expected_hashrate: if expected_hashrate:
hr_list.append(expected_hashrate) hr_list.append(expected_hashrate)
if len(hr_list) == 0: if len(hr_list) == 0:
return AlgoHashRate.SHA256(0) return self.algo.hashrate(
else: rate=float(0), unit=self.algo.unit.default
return AlgoHashRate.SHA256(
(sum(hr_list) / len(hr_list)) * self.expected_hashboards
) )
else:
return self.algo.hashrate(
rate=float(
(sum(hr_list) / len(hr_list)) * self.expected_hashboards
),
unit=self.algo.unit.MH,
).into(self.algo.unit.default)
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
@@ -614,7 +623,7 @@ class BOSMiner(BraiinsOSFirmware):
pass pass
return pools_data return pools_data
async def upgrade_firmware(self, file: Path): async def upgrade_firmware(self, file: Path) -> str:
""" """
Upgrade the firmware of the BOSMiner device. Upgrade the firmware of the BOSMiner device.
@@ -622,7 +631,7 @@ class BOSMiner(BraiinsOSFirmware):
file (Path): The local file path of the firmware to be uploaded. file (Path): The local file path of the firmware to be uploaded.
Returns: Returns:
str: Confirmation message after upgrading the firmware. Confirmation message after upgrading the firmware.
""" """
try: try:
logging.info("Starting firmware upgrade process.") logging.info("Starting firmware upgrade process.")
@@ -737,7 +746,7 @@ class BOSer(BraiinsOSFirmware):
"""Handler for new versions of BraiinsOS+ (post-gRPC)""" """Handler for new versions of BraiinsOS+ (post-gRPC)"""
_rpc_cls = BOSMinerRPCAPI _rpc_cls = BOSMinerRPCAPI
web: BOSMinerRPCAPI rpc: BOSMinerRPCAPI
_web_cls = BOSerWebAPI _web_cls = BOSerWebAPI
web: BOSerWebAPI web: BOSerWebAPI
@@ -889,8 +898,9 @@ class BOSer(BraiinsOSFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return self.algo.hashrate(
rpc_summary["SUMMARY"][0]["MHS 1m"], HashUnit.SHA256.MH rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
unit=self.algo.unit.MH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
pass pass
@@ -906,9 +916,11 @@ class BOSer(BraiinsOSFirmware):
if grpc_miner_details is not None: if grpc_miner_details is not None:
try: try:
return AlgoHashRate.SHA256( return self.algo.hashrate(
grpc_miner_details["stickerHashrate"]["gigahashPerSecond"], rate=float(
HashUnit.SHA256.GH, grpc_miner_details["stickerHashrate"]["gigahashPerSecond"]
),
unit=self.algo.unit.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass
@@ -933,18 +945,20 @@ class BOSer(BraiinsOSFirmware):
if board.get("chipsCount") is not None: if board.get("chipsCount") is not None:
hashboards[idx].chips = board["chipsCount"] hashboards[idx].chips = board["chipsCount"]
if board.get("boardTemp") is not None: if board.get("boardTemp") is not None:
hashboards[idx].temp = board["boardTemp"]["degreeC"] hashboards[idx].temp = int(board["boardTemp"]["degreeC"])
if board.get("highestChipTemp") is not None: if board.get("highestChipTemp") is not None:
hashboards[idx].chip_temp = board["highestChipTemp"]["temperature"][ hashboards[idx].chip_temp = int(
"degreeC" board["highestChipTemp"]["temperature"]["degreeC"]
] )
if board.get("stats") is not None: if board.get("stats") is not None:
if not board["stats"]["realHashrate"]["last5S"] == {}: if not board["stats"]["realHashrate"]["last5S"] == {}:
hashboards[idx].hashrate = AlgoHashRate.SHA256( hashboards[idx].hashrate = self.algo.hashrate(
board["stats"]["realHashrate"]["last5S"][ rate=float(
"gigahashPerSecond" board["stats"]["realHashrate"]["last5S"][
], "gigahashPerSecond"
HashUnit.SHA256.GH, ]
),
unit=self.algo.unit.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[idx].missing = False hashboards[idx].missing = False
@@ -993,7 +1007,7 @@ class BOSer(BraiinsOSFirmware):
fans = [] fans = []
for n in range(self.expected_fans): for n in range(self.expected_fans):
try: try:
fans.append(Fan(grpc_cooling_state["fans"][n]["rpm"])) fans.append(Fan(speed=grpc_cooling_state["fans"][n]["rpm"]))
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
return fans return fans
@@ -1024,7 +1038,9 @@ class BOSer(BraiinsOSFirmware):
]: ]:
_error = board["Status"].split(" {")[0] _error = board["Status"].split(" {")[0]
_error = _error[0].lower() + _error[1:] _error = _error[0].lower() + _error[1:]
errors.append(BraiinsOSError(f"Slot {_id} {_error}")) errors.append(
BraiinsOSError(error_message=f"Slot {_id} {_error}")
)
return errors return errors
except LookupError: except LookupError:
pass pass
@@ -1085,7 +1101,7 @@ class BOSer(BraiinsOSFirmware):
for group in grpc_pool_groups["poolGroups"]: for group in grpc_pool_groups["poolGroups"]:
for idx, pool_info in enumerate(group["pools"]): for idx, pool_info in enumerate(group["pools"]):
pool_data = PoolMetrics( pool_data = PoolMetrics(
url=pool_info["url"], url=PoolUrl.from_str(pool_info["url"]),
user=pool_info["user"], user=pool_info["user"],
index=idx, index=idx,
accepted=pool_info["stats"].get("acceptedShares", 0), accepted=pool_info["stats"].get("acceptedShares", 0),

View File

@@ -21,9 +21,10 @@ from typing import List, Optional
import aiofiles import aiofiles
from pyasic.config import MinerConfig, MiningModeConfig from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, WhatsminerError from pyasic.data.error_codes import MinerErrorData, WhatsminerError
from pyasic.data.pools import PoolMetrics, PoolUrl from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.device.algorithm import AlgoHashRate
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.firmware import StockFirmware from pyasic.miners.device.firmware import StockFirmware
@@ -274,7 +275,7 @@ class BTMiner(StockFirmware):
cfg.mining_mode = MiningModeConfig.normal() cfg.mining_mode = MiningModeConfig.normal()
return cfg return cfg
cfg.mining_mode = MiningModeConfig.power_tuning(power_lim) cfg.mining_mode = MiningModeConfig.power_tuning(power=power_lim)
self.config = cfg self.config = cfg
return self.config return self.config
@@ -403,8 +404,9 @@ class BTMiner(StockFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return self.algo.hashrate(
rpc_summary["SUMMARY"][0]["MHS 1m"], HashUnit.SHA256.MH rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
unit=self.algo.unit.MH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass
@@ -433,8 +435,8 @@ class BTMiner(StockFirmware):
self.expected_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 = AlgoHashRate.SHA256( hashboards[board["ASC"]].hashrate = self.algo.hashrate(
board["MHS 1m"], HashUnit.SHA256.MH rate=float(board["MHS 1m"]), unit=self.algo.unit.MH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[board["ASC"]].chips = board["Effective Chips"] hashboards[board["ASC"]].chips = board["Effective Chips"]
hashboards[board["ASC"]].serial_number = board["PCB SN"] hashboards[board["ASC"]].serial_number = board["PCB SN"]
@@ -498,8 +500,8 @@ class BTMiner(StockFirmware):
try: try:
if self.expected_fans > 0: if self.expected_fans > 0:
fans = [ fans = [
Fan(rpc_summary["SUMMARY"][0].get("Fan Speed In", 0)), Fan(speed=rpc_summary["SUMMARY"][0].get("Fan Speed In", 0)),
Fan(rpc_summary["SUMMARY"][0].get("Fan Speed Out", 0)), Fan(speed=rpc_summary["SUMMARY"][0].get("Fan Speed Out", 0)),
] ]
except LookupError: except LookupError:
pass pass
@@ -583,8 +585,8 @@ class BTMiner(StockFirmware):
try: try:
expected_hashrate = rpc_summary["SUMMARY"][0]["Factory GHS"] expected_hashrate = rpc_summary["SUMMARY"][0]["Factory GHS"]
if expected_hashrate: if expected_hashrate:
return AlgoHashRate.SHA256( return self.algo.hashrate(
expected_hashrate, HashUnit.SHA256.GH rate=float(expected_hashrate), unit=self.algo.unit.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
@@ -690,7 +692,7 @@ class BTMiner(StockFirmware):
pass pass
return pools_data return pools_data
async def upgrade_firmware(self, file: Path): async def upgrade_firmware(self, file: Path) -> str:
""" """
Upgrade the firmware of the Whatsminer device. Upgrade the firmware of the Whatsminer device.

View File

@@ -17,8 +17,8 @@
from typing import List, Optional from typing import List, Optional
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import AlgoHashRate, HashUnit
from pyasic.data.pools import PoolMetrics, PoolUrl from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.device.algorithm import AlgoHashRate
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.firmware import StockFirmware from pyasic.miners.device.firmware import StockFirmware
@@ -123,8 +123,9 @@ class CGMiner(StockFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return self.algo.hashrate(
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
unit=self.algo.unit.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass

View File

@@ -18,9 +18,10 @@ from pathlib import Path
from typing import List, Optional from typing import List, Optional
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.data.pools import PoolMetrics from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.device.algorithm import AlgoHashRate
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger from pyasic.logger import logger
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
@@ -234,9 +235,9 @@ class ePIC(ePICFirmware):
if web_summary["HBs"] is not None: if web_summary["HBs"] is not None:
for hb in web_summary["HBs"]: for hb in web_summary["HBs"]:
hashrate += hb["Hashrate"][0] hashrate += hb["Hashrate"][0]
return AlgoHashRate.SHA256(hashrate, HashUnit.SHA256.MH).into( return self.algo.hashrate(
HashUnit.SHA256.TH rate=float(hashrate), unit=self.algo.unit.MH
) ).into(self.algo.unit.TH)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -260,9 +261,9 @@ class ePIC(ePICFirmware):
ideal = hb["Hashrate"][1] / 100 ideal = hb["Hashrate"][1] / 100
hashrate += hb["Hashrate"][0] / ideal hashrate += hb["Hashrate"][0] / ideal
return AlgoHashRate.SHA256(hashrate, HashUnit.SHA256.MH).into( return self.algo.hashrate(
self.algo.unit.default rate=float(hashrate), unit=self.algo.unit.MH
) ).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -293,7 +294,7 @@ class ePIC(ePICFirmware):
if web_summary is not None: if web_summary is not None:
for fan in web_summary["Fans Rpm"]: for fan in web_summary["Fans Rpm"]:
try: try:
fans.append(Fan(web_summary["Fans Rpm"][fan])) fans.append(Fan(speed=web_summary["Fans Rpm"][fan]))
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
fans.append(Fan()) fans.append(Fan())
return fans return fans
@@ -352,11 +353,11 @@ class ePIC(ePICFirmware):
hashrate = hb["Hashrate"][0] hashrate = hb["Hashrate"][0]
# Update the Hashboard object # Update the Hashboard object
hb_list[hb["Index"]].missing = False hb_list[hb["Index"]].missing = False
hb_list[hb["Index"]].hashrate = AlgoHashRate.SHA256( hb_list[hb["Index"]].hashrate = self.algo.hashrate(
hashrate, HashUnit.SHA256.MH rate=float(hashrate), unit=self.algo.unit.MH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hb_list[hb["Index"]].chips = num_of_chips hb_list[hb["Index"]].chips = num_of_chips
hb_list[hb["Index"]].temp = hb["Temperature"] hb_list[hb["Index"]].temp = int(hb["Temperature"])
hb_list[hb["Index"]].tuned = tuned hb_list[hb["Index"]].tuned = tuned
hb_list[hb["Index"]].active = active hb_list[hb["Index"]].active = active
hb_list[hb["Index"]].voltage = hb["Input Voltage"] hb_list[hb["Index"]].voltage = hb["Input Voltage"]
@@ -417,7 +418,7 @@ class ePIC(ePICFirmware):
try: try:
error = web_summary["Status"]["Last Error"] error = web_summary["Status"]["Last Error"]
if error is not None: if error is not None:
errors.append(X19Error(str(error))) errors.append(X19Error(error_message=str(error)))
return errors return errors
except KeyError: except KeyError:
pass pass
@@ -437,6 +438,10 @@ class ePIC(ePICFirmware):
web_summary.get("Session") is not None web_summary.get("Session") is not None
and web_summary.get("Stratum") is not None and web_summary.get("Stratum") is not None
): ):
url = web_summary["Stratum"].get("Current Pool")
# TODO: when scheme gets put in, update this
if url is not None:
url = PoolUrl.from_str(f"stratum+tcp://{url}")
pool_data.append( pool_data.append(
PoolMetrics( PoolMetrics(
accepted=web_summary["Session"].get("Accepted"), accepted=web_summary["Session"].get("Accepted"),
@@ -445,7 +450,7 @@ class ePIC(ePICFirmware):
remote_failures=0, remote_failures=0,
active=web_summary["Stratum"].get("IsPoolConnected"), active=web_summary["Stratum"].get("IsPoolConnected"),
alive=web_summary["Stratum"].get("IsPoolConnected"), alive=web_summary["Stratum"].get("IsPoolConnected"),
url=web_summary["Stratum"].get("Current Pool"), url=url,
user=web_summary["Stratum"].get("Current User"), user=web_summary["Stratum"].get("Current User"),
index=web_summary["Stratum"].get("Config Id"), index=web_summary["Stratum"].get("Config Id"),
) )

View File

@@ -16,7 +16,7 @@
from typing import List from typing import List
from pyasic.config import MinerConfig, MiningModeConfig from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import AlgoHashRate, HashBoard, HashUnit from pyasic.data import HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger from pyasic.logger import logger
from pyasic.miners.backends import BFGMiner from pyasic.miners.backends import BFGMiner
@@ -162,8 +162,8 @@ class GoldshellMiner(BFGMiner):
if board.get("ID") is not None: if board.get("ID") is not None:
try: try:
b_id = board["ID"] b_id = board["ID"]
hashboards[b_id].hashrate = AlgoHashRate.SHA256( hashboards[b_id].hashrate = self.algo.hashrate(
board["MHS 20s"], HashUnit.SHA256.MH rate=float(board["MHS 20s"]), unit=self.algo.unit.MH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[b_id].temp = board["tstemp-2"] hashboards[b_id].temp = board["tstemp-2"]
hashboards[b_id].missing = False hashboards[b_id].missing = False

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