Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5567f26c03 | ||
|
|
0027485582 | ||
|
|
c9bcb7bab6 | ||
|
|
0ee261930e | ||
|
|
3cfc8dded9 | ||
|
|
655cf6d0ac | ||
|
|
640dc6d8c2 | ||
|
|
a572fedb4d | ||
|
|
6c46a7cd71 | ||
|
|
49f42172da | ||
|
|
83be80e4bc | ||
|
|
0be9e9d519 | ||
|
|
4694c0f774 | ||
|
|
11012b310b | ||
|
|
8852fab3ee | ||
|
|
ec1b2ca162 | ||
|
|
1f9d4c8c10 | ||
|
|
5bb04b2af4 | ||
|
|
2e192a1536 | ||
|
|
4da8044bc7 | ||
|
|
76078e4d0e | ||
|
|
276a476fab | ||
|
|
e0abed4f93 | ||
|
|
4a67bd5d99 | ||
|
|
eb48d04939 | ||
|
|
c4697728e4 | ||
|
|
de64172073 | ||
|
|
4d7a13433b | ||
|
|
e14a4791b2 | ||
|
|
0e76d4550b | ||
|
|
2d424025e9 | ||
|
|
bf4903ce4b | ||
|
|
4f7f6bf045 | ||
|
|
824890ec97 | ||
|
|
ce9d7ffb0f | ||
|
|
183b4934c1 | ||
|
|
3d2b260b17 | ||
|
|
f88c1734eb | ||
|
|
b897ca8363 | ||
|
|
dba341fdae | ||
|
|
837794bd57 | ||
|
|
36d16c7235 | ||
|
|
7797023689 | ||
|
|
1021f200ec | ||
|
|
197d6568e3 | ||
|
|
71c8905674 | ||
|
|
15c3806fbf | ||
|
|
a5c42c9c2b | ||
|
|
90d8a795e6 | ||
|
|
07cf1b134c | ||
|
|
53e1f33fa6 | ||
|
|
16ab2bd4e3 | ||
|
|
de728f12d2 | ||
|
|
8092e12dfb | ||
|
|
e70c3e9f79 | ||
|
|
39a97f2914 | ||
|
|
35af74ad1a | ||
|
|
15fa91fb98 | ||
|
|
084987a3e1 | ||
|
|
e93cc77a58 | ||
|
|
788d43c51c | ||
|
|
74c22b82ce | ||
|
|
1f46ce1b9a | ||
|
|
7964336a0c | ||
|
|
a24fc07c2a | ||
|
|
4c64481d3b | ||
|
|
66fb5834f0 | ||
|
|
db05cc1d97 | ||
|
|
418e3ce26e | ||
|
|
5a0bb11a44 | ||
|
|
39f9d087db | ||
|
|
067a376f94 | ||
|
|
a5d6e122f9 | ||
|
|
b160fd75ba | ||
|
|
d1007d3ae8 | ||
|
|
33803e89e2 | ||
|
|
10a44b9877 | ||
|
|
bbd883f639 | ||
|
|
8e2ad478e9 | ||
|
|
957981a9c6 | ||
|
|
13a67dfdd1 | ||
|
|
e86f2b62c5 | ||
|
|
88b4d2cac3 | ||
|
|
5842ef3d97 | ||
|
|
339a689267 |
@@ -1,7 +1,15 @@
|
||||
ci:
|
||||
skip:
|
||||
- poetry-lock
|
||||
- unittest
|
||||
- generate-docs
|
||||
repos:
|
||||
- repo: https://github.com/python-poetry/poetry
|
||||
rev: 2.1.1
|
||||
hooks:
|
||||
- id: poetry-check
|
||||
- id: poetry-lock
|
||||
- id: poetry-install
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
@@ -15,11 +23,11 @@ repos:
|
||||
exclude: ^mkdocs\.yml$
|
||||
- id: check-added-large-files
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 24.10.0
|
||||
rev: 25.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.13.2
|
||||
rev: 6.0.1
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
@@ -30,6 +38,12 @@ repos:
|
||||
name: unittest
|
||||
entry: python -m unittest discover
|
||||
language: system
|
||||
'types': [python]
|
||||
types: [ python ]
|
||||
args: ["-p '*test.py'"] # Probably this option is absolutely not needed.
|
||||
pass_filenames: false
|
||||
- id: generate-docs
|
||||
name: generate-docs
|
||||
entry: python docs/generate_miners.py
|
||||
language: system
|
||||
types: [ python ]
|
||||
pass_filenames: false
|
||||
|
||||
@@ -50,6 +50,13 @@ poetry install --with dev
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
##### Building Documentation Locally
|
||||
```
|
||||
poetry install --with docs
|
||||
python docs/generate_miners.py
|
||||
poetry run mkdocs serve
|
||||
```
|
||||
|
||||
---
|
||||
## Getting started
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import asyncio
|
||||
import importlib
|
||||
import os
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
|
||||
from pyasic.miners.factory import MINER_CLASSES, MinerTypes
|
||||
|
||||
@@ -51,12 +52,19 @@ def backend_str(backend: MinerTypes) -> str:
|
||||
return "Mara Firmware Miners"
|
||||
case MinerTypes.BITAXE:
|
||||
return "Stock Firmware BitAxe Miners"
|
||||
case MinerTypes.LUCKYMINER:
|
||||
return "Stock Firmware Lucky Miners"
|
||||
case MinerTypes.ICERIVER:
|
||||
return "Stock Firmware IceRiver Miners"
|
||||
case MinerTypes.HAMMER:
|
||||
return "Stock Firmware Hammer Miners"
|
||||
case MinerTypes.VOLCMINER:
|
||||
return "Stock Firmware Volcminers"
|
||||
case MinerTypes.ELPHAPEX:
|
||||
return "Stock Firmware Elphapex Miners"
|
||||
case MinerTypes.MSKMINER:
|
||||
return "MSKMiner Firmware Miners"
|
||||
raise TypeError("Unknown miner backend, cannot generate docs")
|
||||
|
||||
|
||||
def create_url_str(mtype: str):
|
||||
@@ -135,14 +143,14 @@ for m in MINER_CLASSES:
|
||||
done.append(miner)
|
||||
|
||||
|
||||
async def create_directory_structure(directory, data):
|
||||
def create_directory_structure(directory, data):
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
for key, value in data.items():
|
||||
subdirectory = os.path.join(directory, key)
|
||||
if isinstance(value, dict):
|
||||
await create_directory_structure(subdirectory, value)
|
||||
create_directory_structure(subdirectory, value)
|
||||
elif isinstance(value, list):
|
||||
file_path = os.path.join(subdirectory + ".md")
|
||||
|
||||
@@ -163,7 +171,7 @@ async def create_directory_structure(directory, data):
|
||||
)
|
||||
|
||||
|
||||
async def create_supported_types(directory):
|
||||
def create_supported_types(directory):
|
||||
with open(os.path.join(directory, "supported_types.md"), "w") as file:
|
||||
file.write(SUPPORTED_TYPES_HEADER)
|
||||
for mback in MINER_CLASSES:
|
||||
@@ -180,7 +188,7 @@ async def create_supported_types(directory):
|
||||
for mtype in backend_types:
|
||||
file.write(MINER_TYPE_HEADER.format(mtype))
|
||||
for minstance in backend_types[mtype]:
|
||||
model = await minstance("1.1.1.1").get_model()
|
||||
model = minstance("1.1.1.1").model
|
||||
file.write(
|
||||
MINER_DETAILS.format(
|
||||
make(minstance), mtype, create_url_str(model), model
|
||||
@@ -190,6 +198,7 @@ async def create_supported_types(directory):
|
||||
file.write(BACKEND_TYPE_CLOSER)
|
||||
|
||||
|
||||
root_directory = os.path.join(os.getcwd(), "miners")
|
||||
asyncio.run(create_directory_structure(root_directory, m_data))
|
||||
asyncio.run(create_supported_types(root_directory))
|
||||
if __name__ == "__main__":
|
||||
root_directory = Path(__file__).parent.joinpath("miners")
|
||||
create_directory_structure(root_directory, m_data)
|
||||
create_supported_types(root_directory)
|
||||
|
||||
@@ -495,6 +495,19 @@
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S19i (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19i
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S19j (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
@@ -664,6 +677,19 @@
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S19k Pro Dual (ePIC)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19kProDual
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S19 (Hive)
|
||||
|
||||
- [ ] Shutdowns
|
||||
@@ -690,6 +716,45 @@
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S19K Pro (Hive)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.hiveon.X19.S19.HiveonS19kPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S19 No PIC (Hive)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.hiveon.X19.S19.HiveonS19NoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S19 No PIC (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.mskminer.X19.S19.MSKMinerS19NoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S19 (LuxOS)
|
||||
|
||||
- [x] Shutdowns
|
||||
|
||||
@@ -14,6 +14,19 @@
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 Hydro (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Hydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 Pro (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
@@ -27,6 +40,19 @@
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21+ (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T21 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
@@ -53,6 +79,19 @@
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 Pro (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T21 (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
|
||||
16
docs/miners/avalonminer/A15X.md
Normal file
16
docs/miners/avalonminer/A15X.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# pyasic
|
||||
## A15X Models
|
||||
|
||||
## Avalon 1566 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A15X.A1566.CGMinerAvalon1566
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
@@ -27,3 +27,16 @@
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## BlockMiner eLITE 1.0 (ePIC)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.blockminer.epic.blockminer.blockminer.ePICBlockMinerELITE1
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
|
||||
16
docs/miners/elphapex/DGX.md
Normal file
16
docs/miners/elphapex/DGX.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# pyasic
|
||||
## DGX Models
|
||||
|
||||
## DG1+ (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.elphapex.daoge.DGX.DG1.ElphapexDG1Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
29
docs/miners/luckyminer/LV.md
Normal file
29
docs/miners/luckyminer/LV.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# pyasic
|
||||
## LV Models
|
||||
|
||||
## LV07 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.luckyminer.espminer.LV.LV07.LuckyMinerLV07
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## LV08 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.luckyminer.espminer.LV.LV08.LuckyMinerLV08
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
@@ -103,8 +103,10 @@ details {
|
||||
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
|
||||
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
|
||||
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
|
||||
<li><a href="../antminer/X21#s21_1-stock">S21+ (Stock)</a></li>
|
||||
<li><a href="../antminer/X21#s21-pro-stock">S21 Pro (Stock)</a></li>
|
||||
<li><a href="../antminer/X21#t21-stock">T21 (Stock)</a></li>
|
||||
<li><a href="../antminer/X21#s21-hydro-stock">S21 Hydro (Stock)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
@@ -553,6 +555,12 @@ details {
|
||||
<li><a href="../avalonminer/nano#avalon-nano-3-stock">Avalon Nano 3 (Stock)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>A15X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A15X#avalon-1566-stock">Avalon 1566 (Stock)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
@@ -652,6 +660,7 @@ details {
|
||||
<summary>X21 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X21#s21-bos_1">S21 (BOS+)</a></li>
|
||||
<li><a href="../antminer/X21#s21-pro-bos_1">S21 Pro (BOS+)</a></li>
|
||||
<li><a href="../antminer/X21#t21-bos_1">T21 (BOS+)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
@@ -694,6 +703,7 @@ details {
|
||||
<li><a href="../antminer/X19#s19-no-pic-vnish">S19 No PIC (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro-vnish">S19 Pro (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-vnish">S19j (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#s19i-vnish">S19i (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
||||
@@ -727,6 +737,7 @@ details {
|
||||
<li><a href="../antminer/X19#s19k-pro-epic">S19k Pro (ePIC)</a></li>
|
||||
<li><a href="../antminer/X19#s19-xp-epic">S19 XP (ePIC)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-dual-epic">S19j Pro Dual (ePIC)</a></li>
|
||||
<li><a href="../antminer/X19#s19k-pro-dual-epic">S19k Pro Dual (ePIC)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
@@ -742,6 +753,7 @@ details {
|
||||
<ul>
|
||||
<li><a href="../blockminer/blockminer#blockminer-520i-epic">BlockMiner 520i (ePIC)</a></li>
|
||||
<li><a href="../blockminer/blockminer#blockminer-720i-epic">BlockMiner 720i (ePIC)</a></li>
|
||||
<li><a href="../blockminer/blockminer#blockminer-elite-1.0-epic">BlockMiner eLITE 1.0 (ePIC)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
@@ -760,6 +772,19 @@ details {
|
||||
<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>
|
||||
<li><a href="../antminer/X19#s19k-pro-hive">S19K Pro (Hive)</a></li>
|
||||
<li><a href="../antminer/X19#s19-no-pic-hive">S19 No PIC (Hive)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>MSKMiner Firmware Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>X19 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X19#s19-no-pic-stock">S19 No PIC (Stock)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
@@ -859,6 +884,18 @@ details {
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Stock Firmware Lucky Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>LV Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../luckyminer/LV#lv08-stock">LV08 (Stock)</a></li>
|
||||
<li><a href="../luckyminer/LV#lv07-stock">LV07 (Stock)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Stock Firmware IceRiver Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
@@ -898,4 +935,15 @@ details {
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Stock Firmware Elphapex Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>DGX Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../elphapex/DGX#dg1_1-stock">DG1+ (Stock)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
277
poetry.lock
generated
277
poetry.lock
generated
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiofiles"
|
||||
@@ -44,19 +44,19 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
|
||||
|
||||
[package.extras]
|
||||
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""]
|
||||
trio = ["trio (>=0.26.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "asyncssh"
|
||||
version = "2.19.0"
|
||||
version = "2.20.0"
|
||||
description = "AsyncSSH: Asynchronous SSHv2 client and server library"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "asyncssh-2.19.0-py3-none-any.whl", hash = "sha256:bb82ac30ff0cb4393fbaf1114e606ad7a4f13d6c4bdaed423c033ee26b455228"},
|
||||
{file = "asyncssh-2.19.0.tar.gz", hash = "sha256:723dead4d068b558708dc66a4ca7e7a93a813aa9416036eccb9af4c03ae2cf30"},
|
||||
{file = "asyncssh-2.20.0-py3-none-any.whl", hash = "sha256:af6888d937c07a4bf31293335a6166b4d87608cdb5957b49547da6ad87ecf174"},
|
||||
{file = "asyncssh-2.20.0.tar.gz", hash = "sha256:020b6e384b2328ef8683908ad8e73de9ec2b9b62fd964571ea957bba98412983"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -74,18 +74,37 @@ pywin32 = ["pywin32 (>=227)"]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
version = "2.16.0"
|
||||
version = "2.17.0"
|
||||
description = "Internationalization utilities"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["docs"]
|
||||
files = [
|
||||
{file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"},
|
||||
{file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"},
|
||||
{file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"},
|
||||
{file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
|
||||
dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""]
|
||||
|
||||
[[package]]
|
||||
name = "backrefs"
|
||||
version = "5.8"
|
||||
description = "A wrapper around re and regex that adds additional back references."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["docs"]
|
||||
files = [
|
||||
{file = "backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d"},
|
||||
{file = "backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b"},
|
||||
{file = "backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486"},
|
||||
{file = "backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585"},
|
||||
{file = "backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc"},
|
||||
{file = "backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
extras = ["regex"]
|
||||
|
||||
[[package]]
|
||||
name = "betterproto"
|
||||
@@ -110,14 +129,14 @@ rust-codec = ["betterproto-rust-codec (==0.1.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.12.14"
|
||||
version = "2025.1.31"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main", "docs"]
|
||||
files = [
|
||||
{file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"},
|
||||
{file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"},
|
||||
{file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
|
||||
{file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -422,20 +441,20 @@ test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.16.1"
|
||||
version = "3.17.0"
|
||||
description = "A platform independent file lock."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
|
||||
{file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
|
||||
{file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"},
|
||||
{file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
|
||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
|
||||
typing = ["typing-extensions (>=4.12.2)"]
|
||||
docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
|
||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"]
|
||||
typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""]
|
||||
|
||||
[[package]]
|
||||
name = "ghp-import"
|
||||
@@ -457,14 +476,14 @@ dev = ["flake8", "markdown", "twine", "wheel"]
|
||||
|
||||
[[package]]
|
||||
name = "griffe"
|
||||
version = "1.5.4"
|
||||
version = "1.6.0"
|
||||
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["docs"]
|
||||
files = [
|
||||
{file = "griffe-1.5.4-py3-none-any.whl", hash = "sha256:ed33af890586a5bebc842fcb919fc694b3dc1bc55b7d9e0228de41ce566b4a1d"},
|
||||
{file = "griffe-1.5.4.tar.gz", hash = "sha256:073e78ad3e10c8378c2f798bd4ef87b92d8411e9916e157fd366a17cc4fd4e52"},
|
||||
{file = "griffe-1.6.0-py3-none-any.whl", hash = "sha256:9f1dfe035d4715a244ed2050dfbceb05b1f470809ed4f6bb10ece5a7302f8dd1"},
|
||||
{file = "griffe-1.6.0.tar.gz", hash = "sha256:eb5758088b9c73ad61c7ac014f3cdfb4c57b5c2fcbfca69996584b702aefa354"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -502,30 +521,30 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "4.1.0"
|
||||
description = "HTTP/2 State-Machine based protocol implementation"
|
||||
version = "4.2.0"
|
||||
description = "Pure-Python HTTP/2 protocol implementation"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"},
|
||||
{file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"},
|
||||
{file = "h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0"},
|
||||
{file = "h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
hpack = ">=4.0,<5"
|
||||
hyperframe = ">=6.0,<7"
|
||||
hpack = ">=4.1,<5"
|
||||
hyperframe = ">=6.1,<7"
|
||||
|
||||
[[package]]
|
||||
name = "hpack"
|
||||
version = "4.0.0"
|
||||
description = "Pure-Python HPACK header compression"
|
||||
version = "4.1.0"
|
||||
description = "Pure-Python HPACK header encoding"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"},
|
||||
{file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"},
|
||||
{file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"},
|
||||
{file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -569,7 +588,7 @@ httpcore = "==1.*"
|
||||
idna = "*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli", "brotlicffi"]
|
||||
brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
|
||||
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
@@ -577,26 +596,26 @@ zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "hyperframe"
|
||||
version = "6.0.1"
|
||||
description = "HTTP/2 framing layer for Python"
|
||||
version = "6.1.0"
|
||||
description = "Pure-Python HTTP/2 framing"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"},
|
||||
{file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"},
|
||||
{file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"},
|
||||
{file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.6.5"
|
||||
version = "2.6.8"
|
||||
description = "File identification library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "identify-2.6.5-py2.py3-none-any.whl", hash = "sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566"},
|
||||
{file = "identify-2.6.5.tar.gz", hash = "sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc"},
|
||||
{file = "identify-2.6.8-py2.py3-none-any.whl", hash = "sha256:83657f0f766a3c8d0eaea16d4ef42494b39b34629a4b3192a9d020d349b3e255"},
|
||||
{file = "identify-2.6.8.tar.gz", hash = "sha256:61491417ea2c0c5c670484fd8abbb34de34cdae1e5f39a73ee65e48e4bb663fc"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -619,27 +638,27 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "8.5.0"
|
||||
version = "8.6.1"
|
||||
description = "Read metadata from Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["docs"]
|
||||
markers = "python_version < \"3.10\""
|
||||
files = [
|
||||
{file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"},
|
||||
{file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"},
|
||||
{file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"},
|
||||
{file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=3.20"
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
|
||||
cover = ["pytest-cov"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
enabler = ["pytest-enabler (>=2.2)"]
|
||||
perf = ["ipython"]
|
||||
test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
|
||||
test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
|
||||
type = ["pytest-mypy"]
|
||||
|
||||
[[package]]
|
||||
@@ -807,18 +826,18 @@ watchdog = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["babel (>=2.9.0)"]
|
||||
min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"]
|
||||
min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-autorefs"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
description = "Automatically link across pages in MkDocs."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["docs"]
|
||||
files = [
|
||||
{file = "mkdocs_autorefs-1.3.0-py3-none-any.whl", hash = "sha256:d180f9778a04e78b7134e31418f238bba56f56d6a8af97873946ff661befffb3"},
|
||||
{file = "mkdocs_autorefs-1.3.0.tar.gz", hash = "sha256:6867764c099ace9025d6ac24fd07b85a98335fbd30107ef01053697c8f46db61"},
|
||||
{file = "mkdocs_autorefs-1.4.0-py3-none-any.whl", hash = "sha256:bad19f69655878d20194acd0162e29a89c3f7e6365ffe54e72aa3fd1072f240d"},
|
||||
{file = "mkdocs_autorefs-1.4.0.tar.gz", hash = "sha256:a9c0aa9c90edbce302c09d050a3c4cb7c76f8b7b2c98f84a7a05f53d00392156"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -846,18 +865,19 @@ pyyaml = ">=5.1"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "9.5.49"
|
||||
version = "9.6.7"
|
||||
description = "Documentation that simply works"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["docs"]
|
||||
files = [
|
||||
{file = "mkdocs_material-9.5.49-py3-none-any.whl", hash = "sha256:c3c2d8176b18198435d3a3e119011922f3e11424074645c24019c2dcf08a360e"},
|
||||
{file = "mkdocs_material-9.5.49.tar.gz", hash = "sha256:3671bb282b4f53a1c72e08adbe04d2481a98f85fed392530051f80ff94a9621d"},
|
||||
{file = "mkdocs_material-9.6.7-py3-none-any.whl", hash = "sha256:8a159e45e80fcaadd9fbeef62cbf928569b93df954d4dc5ba76d46820caf7b47"},
|
||||
{file = "mkdocs_material-9.6.7.tar.gz", hash = "sha256:3e2c1fceb9410056c2d91f334a00cdea3215c28750e00c691c1e46b2a33309b4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
babel = ">=2.10,<3.0"
|
||||
backrefs = ">=5.7.post1,<6.0"
|
||||
colorama = ">=0.4,<1.0"
|
||||
jinja2 = ">=3.0,<4.0"
|
||||
markdown = ">=3.2,<4.0"
|
||||
@@ -866,11 +886,10 @@ 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)"]
|
||||
git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "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)"]
|
||||
|
||||
@@ -1127,14 +1146,14 @@ type = ["mypy (>=1.11.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "4.0.1"
|
||||
version = "4.1.0"
|
||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"},
|
||||
{file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"},
|
||||
{file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"},
|
||||
{file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1177,14 +1196,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.5"
|
||||
version = "2.10.6"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"},
|
||||
{file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"},
|
||||
{file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
|
||||
{file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1194,7 +1213,7 @@ typing-extensions = ">=4.12.2"
|
||||
|
||||
[package.extras]
|
||||
email = ["email-validator (>=2.0.0)"]
|
||||
timezone = ["tzdata"]
|
||||
timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
@@ -1326,14 +1345,14 @@ windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
version = "10.14"
|
||||
version = "10.14.3"
|
||||
description = "Extension pack for Python Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["docs"]
|
||||
files = [
|
||||
{file = "pymdown_extensions-10.14-py3-none-any.whl", hash = "sha256:202481f716cc8250e4be8fce997781ebf7917701b59652458ee47f2401f818b5"},
|
||||
{file = "pymdown_extensions-10.14.tar.gz", hash = "sha256:741bd7c4ff961ba40b7528d32284c53bc436b8b1645e8e37c3e57770b8700a34"},
|
||||
{file = "pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9"},
|
||||
{file = "pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1436,110 +1455,6 @@ files = [
|
||||
[package.dependencies]
|
||||
pyyaml = "*"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2024.11.6"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["docs"]
|
||||
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"
|
||||
@@ -1667,21 +1582,21 @@ files = [
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
|
||||
brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
|
||||
h2 = ["h2 (>=4,<5)"]
|
||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.28.1"
|
||||
version = "20.29.2"
|
||||
description = "Virtual Python Environment builder"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "virtualenv-20.28.1-py3-none-any.whl", hash = "sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb"},
|
||||
{file = "virtualenv-20.28.1.tar.gz", hash = "sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329"},
|
||||
{file = "virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a"},
|
||||
{file = "virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1691,7 +1606,7 @@ platformdirs = ">=3.9.1,<5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
|
||||
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
|
||||
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""]
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
@@ -1750,14 +1665,14 @@ files = [
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
|
||||
cover = ["pytest-cov"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
enabler = ["pytest-enabler (>=2.2)"]
|
||||
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
|
||||
test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
|
||||
type = ["pytest-mypy"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "c0aa00dd5f3b52bbac53eb765be2bca2ec7f9429e835d6b2fe6bf207f2f39974"
|
||||
python-versions = ">3.9, <4.0"
|
||||
content-hash = "67a8e0d34c0d1af0f8e4d617d80fd5afc8d197e5d5444f78fd82e4f716a52965"
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
import importlib.metadata
|
||||
|
||||
from pyasic import settings
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import MinerData
|
||||
@@ -22,3 +24,5 @@ from pyasic.network import MinerNetwork
|
||||
from pyasic.rpc import *
|
||||
from pyasic.ssh import *
|
||||
from pyasic.web import *
|
||||
|
||||
__version__ = importlib.metadata.version("pyasic")
|
||||
|
||||
@@ -56,6 +56,26 @@ class MinerConfig(BaseModel):
|
||||
**self.temperature.as_am_modern(),
|
||||
}
|
||||
|
||||
def as_hiveon_modern(self, user_suffix: str | None = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for modern Hiveon."""
|
||||
return {
|
||||
**self.fan_mode.as_hiveon_modern(),
|
||||
"freq-level": "100",
|
||||
**self.mining_mode.as_hiveon_modern(),
|
||||
**self.pools.as_hiveon_modern(user_suffix=user_suffix),
|
||||
**self.temperature.as_hiveon_modern(),
|
||||
}
|
||||
|
||||
def as_elphapex(self, user_suffix: str | None = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for modern Elphapex."""
|
||||
return {
|
||||
**self.fan_mode.as_elphapex(),
|
||||
"fc-freq-level": "100",
|
||||
**self.mining_mode.as_elphapex(),
|
||||
**self.pools.as_elphapex(user_suffix=user_suffix),
|
||||
**self.temperature.as_elphapex(),
|
||||
}
|
||||
|
||||
def as_wm(self, user_suffix: str | None = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Whatsminers."""
|
||||
return {
|
||||
@@ -143,12 +163,12 @@ class MinerConfig(BaseModel):
|
||||
**self.pools.as_mara(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
def as_bitaxe(self, user_suffix: str | None = None) -> dict:
|
||||
def as_espminer(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_bitaxe(),
|
||||
**self.temperature.as_bitaxe(),
|
||||
**self.mining_mode.as_bitaxe(),
|
||||
**self.pools.as_bitaxe(user_suffix=user_suffix),
|
||||
**self.fan_mode.as_espminer(),
|
||||
**self.temperature.as_espminer(),
|
||||
**self.mining_mode.as_espminer(),
|
||||
**self.pools.as_espminer(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
def as_luxos(self, user_suffix: str | None = None) -> dict:
|
||||
@@ -199,6 +219,24 @@ class MinerConfig(BaseModel):
|
||||
fan_mode=FanModeConfig.from_am_modern(web_conf),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_hiveon_modern(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Hiveon."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_hiveon_modern(web_conf),
|
||||
mining_mode=MiningModeConfig.from_hiveon_modern(web_conf),
|
||||
fan_mode=FanModeConfig.from_hiveon_modern(web_conf),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for modern Antminers."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_elphapex(web_conf),
|
||||
mining_mode=MiningModeConfig.from_elphapex(web_conf),
|
||||
fan_mode=FanModeConfig.from_elphapex(web_conf),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_am_old(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for old versions of Antminers."""
|
||||
@@ -272,10 +310,10 @@ class MinerConfig(BaseModel):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict) -> "MinerConfig":
|
||||
def from_espminer(cls, web_system_info: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
pools=PoolConfig.from_bitaxe(web_system_info),
|
||||
fan_mode=FanModeConfig.from_bitaxe(web_system_info),
|
||||
pools=PoolConfig.from_espminer(web_system_info),
|
||||
fan_mode=FanModeConfig.from_espminer(web_system_info),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -308,7 +346,3 @@ class MinerConfig(BaseModel):
|
||||
@classmethod
|
||||
def from_hammer(cls, *args, **kwargs) -> "MinerConfig":
|
||||
return cls.from_am_modern(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_hiveon_modern(cls, web_conf: dict) -> "MinerConfig":
|
||||
return cls.from_am_modern(web_conf)
|
||||
|
||||
@@ -28,6 +28,9 @@ class MinerConfigOption(Enum):
|
||||
def as_am_modern(self) -> dict:
|
||||
return self.value.as_am_modern()
|
||||
|
||||
def as_hiveon_modern(self) -> dict:
|
||||
return self.value.as_hiveon_modern()
|
||||
|
||||
def as_am_old(self) -> dict:
|
||||
return self.value.as_am_old()
|
||||
|
||||
@@ -61,12 +64,15 @@ class MinerConfigOption(Enum):
|
||||
def as_mara(self) -> dict:
|
||||
return self.value.as_mara()
|
||||
|
||||
def as_bitaxe(self) -> dict:
|
||||
return self.value.as_bitaxe()
|
||||
def as_espminer(self) -> dict:
|
||||
return self.value.as_espminer()
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return self.value.as_luxos()
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return self.value.as_elphapex()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.value(*args, **kwargs)
|
||||
|
||||
@@ -92,6 +98,9 @@ class MinerConfigValue(BaseModel):
|
||||
def as_am_modern(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_hiveon_modern(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_am_old(self) -> dict:
|
||||
return {}
|
||||
|
||||
@@ -125,12 +134,15 @@ class MinerConfigValue(BaseModel):
|
||||
def as_mara(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_bitaxe(self) -> dict:
|
||||
def as_espminer(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {}
|
||||
|
||||
def __getitem__(self, item):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
|
||||
@@ -55,6 +55,12 @@ class FanModeNormal(MinerConfigValue):
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"}
|
||||
|
||||
def as_hiveon_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"fc-fan-ctrl": False, "fc-fan-pwn": "100"}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {
|
||||
"temp_control": {"mode": "auto"},
|
||||
@@ -81,7 +87,7 @@ class FanModeNormal(MinerConfigValue):
|
||||
},
|
||||
}
|
||||
|
||||
def as_bitaxe(self) -> dict:
|
||||
def as_espminer(self) -> dict:
|
||||
return {"autoFanspeed": 1}
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
@@ -135,6 +141,12 @@ class FanModeManual(MinerConfigValue):
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": str(self.speed)}
|
||||
|
||||
def as_hiveon_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": str(self.speed)}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"fc-fan-ctrl": True, "fc-fan-pwm": str(self.speed)}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {
|
||||
"temp_control": {"mode": "manual"},
|
||||
@@ -156,7 +168,7 @@ class FanModeManual(MinerConfigValue):
|
||||
},
|
||||
}
|
||||
|
||||
def as_bitaxe(self) -> dict:
|
||||
def as_espminer(self) -> dict:
|
||||
return {"autoFanspeed": 0, "fanspeed": self.speed}
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
@@ -185,6 +197,12 @@ class FanModeImmersion(MinerConfigValue):
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": "0"}
|
||||
|
||||
def as_hiveon_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": "0"}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"fc-fan-ctrl": True, "fc-fan-pwm": "0"}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {
|
||||
"fan_control": {"min_fans": 0},
|
||||
@@ -239,6 +257,34 @@ class FanModeConfig(MinerConfigOption):
|
||||
else:
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_hiveon_modern(cls, web_conf: dict):
|
||||
if web_conf.get("bitmain-fan-ctrl") is not None:
|
||||
fan_manual = web_conf["bitmain-fan-ctrl"]
|
||||
if fan_manual:
|
||||
speed = int(web_conf["bitmain-fan-pwm"])
|
||||
if speed == 0:
|
||||
return cls.immersion()
|
||||
return cls.manual(speed=speed)
|
||||
else:
|
||||
return cls.normal()
|
||||
else:
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_conf: dict):
|
||||
if web_conf.get("fc-fan-ctrl") is not None:
|
||||
fan_manual = web_conf["fc-fan-ctrl"]
|
||||
if fan_manual:
|
||||
speed = int(web_conf["fc-fan-pwm"])
|
||||
if speed == 0:
|
||||
return cls.immersion()
|
||||
return cls.manual(speed=speed)
|
||||
else:
|
||||
return cls.normal()
|
||||
else:
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict):
|
||||
try:
|
||||
@@ -342,7 +388,7 @@ class FanModeConfig(MinerConfigOption):
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict):
|
||||
def from_espminer(cls, web_system_info: dict):
|
||||
if web_system_info["autofanspeed"] == 1:
|
||||
return cls.normal()
|
||||
else:
|
||||
|
||||
@@ -52,6 +52,14 @@ class MiningModeNormal(MinerConfigValue):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
@@ -74,6 +82,9 @@ class MiningModeNormal(MinerConfigValue):
|
||||
def as_luxos(self) -> dict:
|
||||
return {"autotunerset": {"enabled": False}}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"autotuning": {"enabled": True}}
|
||||
|
||||
|
||||
class MiningModeSleep(MinerConfigValue):
|
||||
mode: str = field(init=False, default="sleep")
|
||||
@@ -87,6 +98,14 @@ class MiningModeSleep(MinerConfigValue):
|
||||
return {"miner-mode": "1"}
|
||||
return {"miner-mode": 1}
|
||||
|
||||
def as_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "1"}
|
||||
return {"miner-mode": 1}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 1}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
@@ -119,6 +138,14 @@ class MiningModeLPM(MinerConfigValue):
|
||||
return {"miner-mode": "3"}
|
||||
return {"miner-mode": 3}
|
||||
|
||||
def as_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "3"}
|
||||
return {"miner-mode": 3}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 3}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
@@ -141,6 +168,14 @@ class MiningModeHPM(MinerConfigValue):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
@@ -174,6 +209,14 @@ class MiningModePowerTune(MinerConfigValue):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
if self.power is not None:
|
||||
return {"mode": self.mode, self.mode: {"wattage": self.power}}
|
||||
@@ -215,18 +258,17 @@ class MiningModePowerTune(MinerConfigValue):
|
||||
sd_cfg = {}
|
||||
if self.scaling.shutdown is not None:
|
||||
sd_cfg = self.scaling.shutdown.as_boser()
|
||||
cfg["set_dps"] = (
|
||||
SetDpsRequest(
|
||||
enable=True,
|
||||
**sd_cfg,
|
||||
target=DpsTarget(
|
||||
power_target=DpsPowerTarget(
|
||||
power_step=Power(self.scaling.step),
|
||||
min_power_target=Power(self.scaling.minimum),
|
||||
)
|
||||
),
|
||||
cfg["set_dps"] = SetDpsRequest(
|
||||
enable=True,
|
||||
**sd_cfg,
|
||||
target=DpsTarget(
|
||||
power_target=DpsPowerTarget(
|
||||
power_step=Power(self.scaling.step),
|
||||
min_power_target=Power(self.scaling.minimum),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
return cfg
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
@@ -273,6 +315,14 @@ class MiningModeHashrateTune(MinerConfigValue):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
conf = {"enabled": True, "mode": "hashrate_target"}
|
||||
if self.hashrate is not None:
|
||||
@@ -299,18 +349,17 @@ class MiningModeHashrateTune(MinerConfigValue):
|
||||
sd_cfg = {}
|
||||
if self.scaling.shutdown is not None:
|
||||
sd_cfg = self.scaling.shutdown.as_boser()
|
||||
cfg["set_dps"] = (
|
||||
SetDpsRequest(
|
||||
enable=True,
|
||||
**sd_cfg,
|
||||
target=DpsTarget(
|
||||
hashrate_target=DpsHashrateTarget(
|
||||
hashrate_step=TeraHashrate(self.scaling.step),
|
||||
min_hashrate_target=TeraHashrate(self.scaling.minimum),
|
||||
)
|
||||
),
|
||||
cfg["set_dps"] = SetDpsRequest(
|
||||
enable=True,
|
||||
**sd_cfg,
|
||||
target=DpsTarget(
|
||||
hashrate_target=DpsHashrateTarget(
|
||||
hashrate_step=TeraHashrate(self.scaling.step),
|
||||
min_hashrate_target=TeraHashrate(self.scaling.minimum),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
return cfg
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
@@ -368,20 +417,25 @@ class MiningModePreset(MinerConfigValue):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_luxos(
|
||||
cls, rpc_config: dict, rpc_profiles: list[dict]
|
||||
) -> "MiningModePreset":
|
||||
def from_luxos(cls, rpc_config: dict, rpc_profiles: dict) -> "MiningModePreset":
|
||||
active_preset = cls.get_active_preset_from_luxos(rpc_config, rpc_profiles)
|
||||
return cls(
|
||||
active_preset=active_preset,
|
||||
available_presets=[
|
||||
MiningPreset.from_luxos(p) for p in rpc_profiles["PROFILES"]
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_active_preset_from_luxos(
|
||||
cls, rpc_config: dict, rpc_profiles: dict
|
||||
) -> MiningPreset:
|
||||
active_preset = None
|
||||
active_profile = rpc_config["CONFIG"][0]["Profile"]
|
||||
for profile in rpc_profiles["PROFILES"]:
|
||||
if profile["Profile Name"] == active_profile:
|
||||
active_preset = profile
|
||||
return cls(
|
||||
active_preset=MiningPreset.from_luxos(active_preset),
|
||||
available_presets=[
|
||||
MiningPreset.from_luxos(p) for p in rpc_profiles["PROFILES"]
|
||||
],
|
||||
)
|
||||
return MiningPreset.from_luxos(active_preset)
|
||||
|
||||
|
||||
class ManualBoardSettings(MinerConfigValue):
|
||||
@@ -397,6 +451,14 @@ class ManualBoardSettings(MinerConfigValue):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
return {"freq": self.freq}
|
||||
|
||||
@@ -421,6 +483,9 @@ class MiningModeManual(MinerConfigValue):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
chains = [b.as_vnish() for b in self.boards.values() if b.freq != 0]
|
||||
return {
|
||||
@@ -518,6 +583,34 @@ class MiningModeConfig(MinerConfigOption):
|
||||
return cls.low()
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_hiveon_modern(cls, web_conf: dict):
|
||||
if web_conf.get("bitmain-work-mode") is not None:
|
||||
work_mode = web_conf["bitmain-work-mode"]
|
||||
if work_mode == "":
|
||||
return cls.default()
|
||||
if int(work_mode) == 0:
|
||||
return cls.normal()
|
||||
elif int(work_mode) == 1:
|
||||
return cls.sleep()
|
||||
elif int(work_mode) == 3:
|
||||
return cls.low()
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_conf: dict):
|
||||
if web_conf.get("fc-work-mode") is not None:
|
||||
work_mode = web_conf["fc-work-mode"]
|
||||
if work_mode == "":
|
||||
return cls.default()
|
||||
if int(work_mode) == 0:
|
||||
return cls.normal()
|
||||
elif int(work_mode) == 1:
|
||||
return cls.sleep()
|
||||
elif int(work_mode) == 3:
|
||||
return cls.low()
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict):
|
||||
try:
|
||||
@@ -600,6 +693,7 @@ class MiningModeConfig(MinerConfigOption):
|
||||
return cls.hashrate_tuning(
|
||||
scaling=ScalingConfig.from_bosminer(toml_conf, mode="hashrate"),
|
||||
)
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict, web_presets: list[dict]):
|
||||
@@ -705,7 +799,11 @@ class MiningModeConfig(MinerConfigOption):
|
||||
|
||||
@classmethod
|
||||
def from_luxos(cls, rpc_config: dict, rpc_profiles: dict):
|
||||
return MiningModePreset.from_luxos(rpc_config, rpc_profiles)
|
||||
preset_info = MiningModePreset.from_luxos(rpc_config, rpc_profiles)
|
||||
return cls.preset(
|
||||
active_preset=preset_info.active_preset,
|
||||
available_presets=preset_info.available_presets,
|
||||
)
|
||||
|
||||
|
||||
MiningMode = TypeVar(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import field
|
||||
from typing import TypeVar, Union
|
||||
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
|
||||
@@ -24,7 +24,14 @@ class MiningPreset(MinerConfigValue):
|
||||
hashrate = None
|
||||
else:
|
||||
power = hr_power_split[0].replace("watt", "").strip()
|
||||
hashrate = hr_power_split[1].replace("TH", "").replace(" LC", "").strip()
|
||||
hashrate = (
|
||||
hr_power_split[1]
|
||||
.replace("TH", "")
|
||||
.replace("GH", "")
|
||||
.replace("MH", "")
|
||||
.replace(" LC", "")
|
||||
.strip()
|
||||
)
|
||||
tuned = web_preset["status"] == "tuned"
|
||||
modded_psu = web_preset["modded_psu_required"]
|
||||
return cls(
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
|
||||
|
||||
|
||||
@@ -43,6 +43,20 @@ class Pool(MinerConfigValue):
|
||||
"pass": self.password,
|
||||
}
|
||||
|
||||
def as_hiveon_modern(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix or ''}",
|
||||
"pass": self.password,
|
||||
}
|
||||
|
||||
def as_elphapex(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix or ''}",
|
||||
"pass": self.password,
|
||||
}
|
||||
|
||||
def as_wm(self, idx: int = 1, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
f"pool_{idx}": self.url,
|
||||
@@ -102,7 +116,7 @@ class Pool(MinerConfigValue):
|
||||
"pass": self.password,
|
||||
}
|
||||
|
||||
def as_bitaxe(self, user_suffix: str | None = None) -> dict:
|
||||
def as_espminer(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"stratumURL": self.url,
|
||||
"stratumUser": f"{self.user}{user_suffix or ''}",
|
||||
@@ -146,6 +160,18 @@ class Pool(MinerConfigValue):
|
||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_hiveon_modern(cls, web_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||
)
|
||||
|
||||
# TODO: check if this is accurate, user/username, pass/password
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pool: dict) -> "Pool":
|
||||
@@ -164,7 +190,7 @@ class Pool(MinerConfigValue):
|
||||
return cls(
|
||||
url=toml_pool_conf["url"],
|
||||
user=toml_pool_conf["user"],
|
||||
password=toml_pool_conf["password"],
|
||||
password=toml_pool_conf.get("password", ""),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -192,7 +218,7 @@ class Pool(MinerConfigValue):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict) -> "Pool":
|
||||
def from_espminer(cls, web_system_info: dict) -> "Pool":
|
||||
url = f"stratum+tcp://{web_system_info['stratumURL']}:{web_system_info['stratumPort']}"
|
||||
return cls(
|
||||
url=url,
|
||||
@@ -235,6 +261,28 @@ class PoolGroup(MinerConfigValue):
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_hiveon_modern(self, user_suffix: str | None = None) -> list:
|
||||
pools = []
|
||||
idx = 0
|
||||
while idx < 3:
|
||||
if len(self.pools) > idx:
|
||||
pools.append(self.pools[idx].as_hiveon_modern(user_suffix=user_suffix))
|
||||
else:
|
||||
pools.append(Pool(url="", user="", password="").as_hiveon_modern())
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_elphapex(self, user_suffix: str | None = None) -> list:
|
||||
pools = []
|
||||
idx = 0
|
||||
while idx < 3:
|
||||
if len(self.pools) > idx:
|
||||
pools.append(self.pools[idx].as_elphapex(user_suffix=user_suffix))
|
||||
else:
|
||||
pools.append(Pool(url="", user="", password="").as_elphapex())
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_wm(self, user_suffix: str | None = None) -> dict:
|
||||
pools = {}
|
||||
idx = 0
|
||||
@@ -306,8 +354,8 @@ class PoolGroup(MinerConfigValue):
|
||||
def as_mara(self, user_suffix: str | None = None) -> list:
|
||||
return [p.as_mara(user_suffix=user_suffix) for p in self.pools]
|
||||
|
||||
def as_bitaxe(self, user_suffix: str | None = None) -> dict:
|
||||
return self.pools[0].as_bitaxe(user_suffix=user_suffix)
|
||||
def as_espminer(self, user_suffix: str | None = None) -> dict:
|
||||
return self.pools[0].as_espminer(user_suffix=user_suffix)
|
||||
|
||||
def as_boser(self, user_suffix: str | None = None) -> PoolGroupConfiguration:
|
||||
return PoolGroupConfiguration(
|
||||
@@ -351,6 +399,20 @@ class PoolGroup(MinerConfigValue):
|
||||
pools.append(Pool.from_am_modern(pool))
|
||||
return cls(pools=pools)
|
||||
|
||||
@classmethod
|
||||
def from_hiveon_modern(cls, web_pool_list: list) -> "PoolGroup":
|
||||
pools = []
|
||||
for pool in web_pool_list:
|
||||
pools.append(Pool.from_hiveon_modern(pool))
|
||||
return cls(pools=pools)
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_pool_list: list) -> "PoolGroup":
|
||||
pools = []
|
||||
for pool in web_pool_list:
|
||||
pools.append(Pool.from_elphapex(pool))
|
||||
return cls(pools=pools)
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pools: list) -> "PoolGroup":
|
||||
return cls(pools=[Pool.from_goldshell(p) for p in web_pools])
|
||||
@@ -395,8 +457,8 @@ class PoolGroup(MinerConfigValue):
|
||||
return cls(pools=[Pool.from_mara(pool_conf) for pool_conf in web_config_pools])
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict) -> "PoolGroup":
|
||||
return cls(pools=[Pool.from_bitaxe(web_system_info)])
|
||||
def from_espminer(cls, web_system_info: dict) -> "PoolGroup":
|
||||
return cls(pools=[Pool.from_espminer(web_system_info)])
|
||||
|
||||
@classmethod
|
||||
def from_iceriver(cls, web_userpanel: dict) -> "PoolGroup":
|
||||
@@ -436,6 +498,16 @@ class PoolConfig(MinerConfigValue):
|
||||
return {"pools": self.groups[0].as_am_modern(user_suffix=user_suffix)}
|
||||
return {"pools": PoolGroup().as_am_modern()}
|
||||
|
||||
def as_hiveon_modern(self, user_suffix: str | None = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_hiveon_modern(user_suffix=user_suffix)}
|
||||
return {"pools": PoolGroup().as_hiveon_modern()}
|
||||
|
||||
def as_elphapex(self, user_suffix: str | None = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_elphapex(user_suffix=user_suffix)}
|
||||
return {"pools": PoolGroup().as_elphapex()}
|
||||
|
||||
def as_wm(self, user_suffix: str | None = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_wm(user_suffix=user_suffix)}
|
||||
@@ -507,8 +579,8 @@ class PoolConfig(MinerConfigValue):
|
||||
return {"pools": self.groups[0].as_mara(user_suffix=user_suffix)}
|
||||
return {"pools": []}
|
||||
|
||||
def as_bitaxe(self, user_suffix: str | None = None) -> dict:
|
||||
return self.groups[0].as_bitaxe(user_suffix=user_suffix)
|
||||
def as_espminer(self, user_suffix: str | None = None) -> dict:
|
||||
return self.groups[0].as_espminer(user_suffix=user_suffix)
|
||||
|
||||
def as_luxos(self, user_suffix: str | None = None) -> dict:
|
||||
return {}
|
||||
@@ -533,10 +605,28 @@ class PoolConfig(MinerConfigValue):
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_conf: dict) -> "PoolConfig":
|
||||
pool_data = web_conf["pools"]
|
||||
try:
|
||||
pool_data = web_conf["pools"]
|
||||
except KeyError:
|
||||
return cls(groups=[])
|
||||
|
||||
return cls(groups=[PoolGroup.from_am_modern(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_hiveon_modern(cls, web_conf: dict) -> "PoolConfig":
|
||||
try:
|
||||
pool_data = web_conf["pools"]
|
||||
except KeyError:
|
||||
return cls(groups=[])
|
||||
|
||||
return cls(groups=[PoolGroup.from_hiveon_modern(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_conf: dict) -> "PoolConfig":
|
||||
pool_data = web_conf["pools"]
|
||||
|
||||
return cls(groups=[PoolGroup.from_elphapex(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pools: list) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_goldshell(web_pools)])
|
||||
@@ -576,8 +666,8 @@ class PoolConfig(MinerConfigValue):
|
||||
return cls(groups=[PoolGroup.from_mara(web_config["pools"])])
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_bitaxe(web_system_info)])
|
||||
def from_espminer(cls, web_system_info: dict) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_espminer(web_system_info)])
|
||||
|
||||
@classmethod
|
||||
def from_iceriver(cls, web_userpanel: dict) -> "PoolConfig":
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import copy
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, List, Union
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field, computed_field
|
||||
|
||||
@@ -24,12 +24,11 @@ from pyasic.config import MinerConfig
|
||||
from pyasic.config.mining import MiningModePowerTune
|
||||
from pyasic.data.pools import PoolMetrics, Scheme
|
||||
from pyasic.device.algorithm.hashrate import AlgoHashRateType
|
||||
from pyasic.device.algorithm.hashrate.base import GenericHashrate
|
||||
|
||||
from ..device.algorithm.hashrate.unit.base import GenericUnit
|
||||
from .boards import HashBoard
|
||||
from .device import DeviceInfo
|
||||
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
||||
from .error_codes.base import BaseMinerError
|
||||
from .fans import Fan
|
||||
|
||||
|
||||
@@ -109,25 +108,18 @@ class MinerData(BaseModel):
|
||||
raw_wattage_limit: int | None = Field(exclude=True, default=None, repr=False)
|
||||
|
||||
# fans
|
||||
fans: List[Fan] = Field(default_factory=list)
|
||||
fans: list[Fan] = Field(default_factory=list)
|
||||
fan_psu: int | None = None
|
||||
|
||||
# boards
|
||||
hashboards: List[HashBoard] = Field(default_factory=list)
|
||||
hashboards: list[HashBoard] = Field(default_factory=list)
|
||||
|
||||
# config
|
||||
config: MinerConfig | None = None
|
||||
fault_light: bool | None = None
|
||||
|
||||
# errors
|
||||
errors: List[
|
||||
Union[
|
||||
WhatsminerError,
|
||||
BraiinsOSError,
|
||||
X19Error,
|
||||
InnosiliconError,
|
||||
]
|
||||
] = Field(default_factory=list)
|
||||
errors: list[BaseMinerError] = Field(default_factory=list)
|
||||
|
||||
# mining state
|
||||
is_mining: bool = True
|
||||
@@ -137,8 +129,10 @@ class MinerData(BaseModel):
|
||||
pools: list[PoolMetrics] = Field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return list(cls.model_fields.keys())
|
||||
def fields(cls) -> set:
|
||||
all_fields = set(cls.model_fields.keys())
|
||||
all_fields.update(set(cls.model_computed_fields.keys()))
|
||||
return all_fields
|
||||
|
||||
def get(self, __key: str, default: Any = None):
|
||||
try:
|
||||
@@ -309,25 +303,25 @@ class MinerData(BaseModel):
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def make(self) -> str:
|
||||
def make(self) -> str | None:
|
||||
if self.device_info.make is not None:
|
||||
return str(self.device_info.make)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def model(self) -> str:
|
||||
def model(self) -> str | None:
|
||||
if self.device_info.model is not None:
|
||||
return str(self.device_info.model)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def firmware(self) -> str:
|
||||
def firmware(self) -> str | None:
|
||||
if self.device_info.firmware is not None:
|
||||
return str(self.device_info.firmware)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def algo(self) -> str:
|
||||
def algo(self) -> str | None:
|
||||
if self.device_info.algo is not None:
|
||||
return str(self.device_info.algo)
|
||||
|
||||
@@ -367,7 +361,9 @@ class MinerData(BaseModel):
|
||||
data_list = [str(data[item]) for item in data]
|
||||
return ",".join(data_list)
|
||||
|
||||
def as_influxdb(self, measurement_name: str = "miner_data") -> str:
|
||||
def as_influxdb(
|
||||
self, measurement_name: str = "miner_data", level_delimiter: str = "."
|
||||
) -> str:
|
||||
"""Get this dataclass as [influxdb line protocol](https://docs.influxdata.com/influxdb/v2.4/reference/syntax/line-protocol/).
|
||||
|
||||
Parameters:
|
||||
@@ -376,54 +372,127 @@ class MinerData(BaseModel):
|
||||
Returns:
|
||||
A influxdb line protocol version of this class.
|
||||
"""
|
||||
tag_data = [measurement_name]
|
||||
|
||||
def serialize_int(key: str, value: int) -> str:
|
||||
return f"{key}={value}"
|
||||
|
||||
def serialize_float(key: str, value: float) -> str:
|
||||
return f"{key}={value}"
|
||||
|
||||
def serialize_str(key: str, value: str) -> str:
|
||||
return f'{key}="{value}"'
|
||||
|
||||
def serialize_algo_hash_rate(key: str, value: AlgoHashRateType) -> str:
|
||||
return f"{key}={round(float(value), 2)}"
|
||||
|
||||
def serialize_list(key: str, value: list[Any]) -> str | None:
|
||||
if len(value) == 0:
|
||||
return None
|
||||
|
||||
list_field_data = []
|
||||
for idx, list_field_val in enumerate(value):
|
||||
item_serialization_func = serialization_map.get(
|
||||
type(list_field_val), lambda _k, _v: None
|
||||
)
|
||||
item_serialized = item_serialization_func(
|
||||
f"{key}{level_delimiter}{idx}", list_field_val
|
||||
)
|
||||
if item_serialized is not None:
|
||||
list_field_data.append(item_serialized)
|
||||
continue
|
||||
for dt in serialization_map_instance:
|
||||
if item_serialized is None:
|
||||
if isinstance(list_field_val, dt):
|
||||
item_serialized = serialization_map_instance[dt](
|
||||
f"{key}{level_delimiter}{idx}", list_field_val
|
||||
)
|
||||
if item_serialized is not None:
|
||||
list_field_data.append(item_serialized)
|
||||
return ",".join(list_field_data)
|
||||
|
||||
def serialize_miner_error(key: str, value: BaseMinerError):
|
||||
return value.as_influxdb(key, level_delimiter=level_delimiter)
|
||||
|
||||
def serialize_fan(key: str, value: Fan) -> str:
|
||||
return f"{key}{level_delimiter}speed={value.speed}"
|
||||
|
||||
def serialize_hashboard(key: str, value: HashBoard) -> str:
|
||||
return value.as_influxdb(key, level_delimiter=level_delimiter)
|
||||
|
||||
def serialize_bool(key: str, value: bool):
|
||||
return f"{key}={str(value).lower()}"
|
||||
|
||||
def serialize_pool_metrics(key: str, value: PoolMetrics):
|
||||
return value.as_influxdb(key, level_delimiter=level_delimiter)
|
||||
|
||||
include = [
|
||||
"uptime",
|
||||
"expected_hashrate",
|
||||
"hashrate",
|
||||
"hashboards",
|
||||
"temperature_avg",
|
||||
"env_temp",
|
||||
"wattage",
|
||||
"wattage_limit",
|
||||
"voltage",
|
||||
"fans",
|
||||
"expected_fans",
|
||||
"fan_psu",
|
||||
"total_chips",
|
||||
"expected_chips",
|
||||
"efficiency",
|
||||
"fault_light",
|
||||
"is_mining",
|
||||
"errors",
|
||||
"pools",
|
||||
]
|
||||
|
||||
serialization_map_instance = {
|
||||
AlgoHashRateType: serialize_algo_hash_rate,
|
||||
BaseMinerError: serialize_miner_error,
|
||||
}
|
||||
serialization_map = {
|
||||
int: serialize_int,
|
||||
float: serialize_float,
|
||||
str: serialize_str,
|
||||
bool: serialize_bool,
|
||||
list: serialize_list,
|
||||
Fan: serialize_fan,
|
||||
HashBoard: serialize_hashboard,
|
||||
PoolMetrics: serialize_pool_metrics,
|
||||
}
|
||||
|
||||
tag_data = [
|
||||
measurement_name,
|
||||
f"ip={str(self.ip)}",
|
||||
f"mac={str(self.mac)}",
|
||||
f"make={str(self.make)}",
|
||||
f"model={str(self.model)}",
|
||||
f"firmware={str(self.firmware)}",
|
||||
f"algo={str(self.algo)}",
|
||||
]
|
||||
field_data = []
|
||||
|
||||
tags = ["ip", "mac", "model", "hostname"]
|
||||
for attribute in self.fields():
|
||||
if attribute in tags:
|
||||
escaped_data = self.get(attribute, "Unknown").replace(" ", "\\ ")
|
||||
tag_data.append(f"{attribute}={escaped_data}")
|
||||
for field in include:
|
||||
field_val = getattr(self, field)
|
||||
serialization_func = serialization_map.get(
|
||||
type(field_val), lambda _k, _v: None
|
||||
)
|
||||
serialized = serialization_func(field, field_val)
|
||||
if serialized is not None:
|
||||
field_data.append(serialized)
|
||||
continue
|
||||
elif str(attribute).startswith("_"):
|
||||
continue
|
||||
elif isinstance(self[attribute], str):
|
||||
field_data.append(f'{attribute}="{self[attribute]}"')
|
||||
continue
|
||||
elif isinstance(self[attribute], bool):
|
||||
field_data.append(f"{attribute}={str(self[attribute]).lower()}")
|
||||
continue
|
||||
elif isinstance(self[attribute], int):
|
||||
field_data.append(f"{attribute}={self[attribute]}")
|
||||
continue
|
||||
elif isinstance(self[attribute], float):
|
||||
field_data.append(f"{attribute}={self[attribute]}")
|
||||
continue
|
||||
elif attribute == "errors":
|
||||
for idx, item in enumerate(self[attribute]):
|
||||
field_data.append(f'error_{idx+1}="{item.error_message}"')
|
||||
elif attribute == "hashboards":
|
||||
for idx, item in enumerate(self[attribute]):
|
||||
field_data.append(
|
||||
f"hashboard_{idx+1}_hashrate={item.get('hashrate', 0.0)}"
|
||||
)
|
||||
field_data.append(
|
||||
f"hashboard_{idx+1}_temperature={item.get('temp', 0)}"
|
||||
)
|
||||
field_data.append(
|
||||
f"hashboard_{idx+1}_chip_temperature={item.get('chip_temp', 0)}"
|
||||
)
|
||||
field_data.append(f"hashboard_{idx+1}_chips={item.get('chips', 0)}")
|
||||
field_data.append(
|
||||
f"hashboard_{idx+1}_expected_chips={item.get('expected_chips', 0)}"
|
||||
)
|
||||
elif attribute == "fans":
|
||||
for idx, item in enumerate(self[attribute]):
|
||||
if item.speed is not None:
|
||||
field_data.append(f"fan_{idx+1}={item.speed}")
|
||||
for datatype in serialization_map_instance:
|
||||
if serialized is None:
|
||||
if isinstance(field_val, datatype):
|
||||
serialized = serialization_map_instance[datatype](
|
||||
field, field_val
|
||||
)
|
||||
if serialized is not None:
|
||||
field_data.append(serialized)
|
||||
|
||||
tags_str = ",".join(tag_data)
|
||||
field_str = ",".join(field_data)
|
||||
timestamp = str(self.timestamp * 1e9)
|
||||
tags_str = ",".join(tag_data).replace(" ", "\\ ")
|
||||
field_str = ",".join(field_data).replace(" ", "\\ ")
|
||||
timestamp = str(self.timestamp * 10**9)
|
||||
|
||||
return " ".join([tags_str, field_str, timestamp])
|
||||
|
||||
@@ -17,7 +17,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, field_serializer
|
||||
from pydantic import BaseModel
|
||||
|
||||
from pyasic.device.algorithm.hashrate import AlgoHashRateType
|
||||
|
||||
@@ -51,6 +51,12 @@ class HashBoard(BaseModel):
|
||||
active: bool | None = None
|
||||
voltage: float | None = None
|
||||
|
||||
@classmethod
|
||||
def fields(cls) -> set:
|
||||
all_fields = set(cls.model_fields.keys())
|
||||
all_fields.update(set(cls.model_computed_fields.keys()))
|
||||
return all_fields
|
||||
|
||||
def get(self, __key: str, default: Any = None):
|
||||
try:
|
||||
val = self.__getitem__(__key)
|
||||
@@ -65,3 +71,63 @@ class HashBoard(BaseModel):
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
raise KeyError(f"{item}")
|
||||
|
||||
def as_influxdb(self, key_root: str, level_delimiter: str = ".") -> str:
|
||||
|
||||
def serialize_int(key: str, value: int) -> str:
|
||||
return f"{key}={value}"
|
||||
|
||||
def serialize_float(key: str, value: float) -> str:
|
||||
return f"{key}={value}"
|
||||
|
||||
def serialize_str(key: str, value: str) -> str:
|
||||
return f'{key}="{value}"'
|
||||
|
||||
def serialize_algo_hash_rate(key: str, value: AlgoHashRateType) -> str:
|
||||
return f"{key}={round(float(value), 2)}"
|
||||
|
||||
def serialize_bool(key: str, value: bool):
|
||||
return f"{key}={str(value).lower()}"
|
||||
|
||||
serialization_map_instance = {
|
||||
AlgoHashRateType: serialize_algo_hash_rate,
|
||||
}
|
||||
serialization_map = {
|
||||
int: serialize_int,
|
||||
float: serialize_float,
|
||||
str: serialize_str,
|
||||
bool: serialize_bool,
|
||||
}
|
||||
|
||||
include = [
|
||||
"hashrate",
|
||||
"temp",
|
||||
"chip_temp",
|
||||
"chips",
|
||||
"expected_chips",
|
||||
"tuned",
|
||||
"active",
|
||||
"voltage",
|
||||
]
|
||||
|
||||
field_data = []
|
||||
for field in include:
|
||||
field_val = getattr(self, field)
|
||||
serialization_func = serialization_map.get(
|
||||
type(field_val), lambda _k, _v: None
|
||||
)
|
||||
serialized = serialization_func(
|
||||
f"{key_root}{level_delimiter}{field}", field_val
|
||||
)
|
||||
if serialized is not None:
|
||||
field_data.append(serialized)
|
||||
continue
|
||||
for datatype in serialization_map_instance:
|
||||
if serialized is None:
|
||||
if isinstance(field_val, datatype):
|
||||
serialized = serialization_map_instance[datatype](
|
||||
f"{key_root}{level_delimiter}{field}", field_val
|
||||
)
|
||||
if serialized is not None:
|
||||
field_data.append(serialized)
|
||||
return ",".join(field_data)
|
||||
|
||||
@@ -18,9 +18,15 @@ from typing import TypeVar
|
||||
|
||||
from .bos import BraiinsOSError
|
||||
from .innosilicon import InnosiliconError
|
||||
from .vnish import VnishError
|
||||
from .whatsminer import WhatsminerError
|
||||
from .X19 import X19Error
|
||||
|
||||
MinerErrorData = TypeVar(
|
||||
"MinerErrorData", WhatsminerError, BraiinsOSError, X19Error, InnosiliconError
|
||||
"MinerErrorData",
|
||||
WhatsminerError,
|
||||
BraiinsOSError,
|
||||
X19Error,
|
||||
InnosiliconError,
|
||||
VnishError,
|
||||
)
|
||||
|
||||
@@ -16,3 +16,17 @@ class BaseMinerError(BaseModel):
|
||||
A dictionary version of this class.
|
||||
"""
|
||||
return self.asdict()
|
||||
|
||||
def as_influxdb(self, root_key: str, level_delimiter: str = ".") -> str:
|
||||
field_data = []
|
||||
|
||||
if self.error_code is not None:
|
||||
field_data.append(
|
||||
f"{root_key}{level_delimiter}error_code={self.error_code}"
|
||||
)
|
||||
if self.error_message is not None:
|
||||
field_data.append(
|
||||
f'{root_key}{level_delimiter}error_message="{self.error_message}"'
|
||||
)
|
||||
|
||||
return ",".join(field_data)
|
||||
|
||||
28
pyasic/data/error_codes/vnish.py
Normal file
28
pyasic/data/error_codes/vnish.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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.data.error_codes.base import BaseMinerError
|
||||
|
||||
|
||||
class VnishError(BaseMinerError):
|
||||
"""A Dataclass to handle error codes of Vnish miners.
|
||||
|
||||
Attributes:
|
||||
error_message: The error message as a string.
|
||||
error_code: The error code as an int. 0 if the message is not assigned a code.
|
||||
"""
|
||||
|
||||
error_message: str
|
||||
error_code: int = 0
|
||||
@@ -92,3 +92,51 @@ class PoolMetrics(BaseModel):
|
||||
if total == 0:
|
||||
return 0
|
||||
return (value / total) * 100
|
||||
|
||||
def as_influxdb(self, key_root: str, level_delimiter: str = ".") -> str:
|
||||
|
||||
def serialize_int(key: str, value: int) -> str:
|
||||
return f"{key}={value}"
|
||||
|
||||
def serialize_float(key: str, value: float) -> str:
|
||||
return f"{key}={value}"
|
||||
|
||||
def serialize_str(key: str, value: str) -> str:
|
||||
return f'{key}="{value}"'
|
||||
|
||||
def serialize_pool_url(key: str, value: str) -> str:
|
||||
return f'{key}="{str(value)}"'
|
||||
|
||||
def serialize_bool(key: str, value: bool):
|
||||
return f"{key}={str(value).lower()}"
|
||||
|
||||
serialization_map = {
|
||||
int: serialize_int,
|
||||
float: serialize_float,
|
||||
str: serialize_str,
|
||||
bool: serialize_bool,
|
||||
PoolUrl: serialize_pool_url,
|
||||
}
|
||||
|
||||
include = [
|
||||
"url",
|
||||
"accepted",
|
||||
"rejected",
|
||||
"active",
|
||||
"alive",
|
||||
"user",
|
||||
]
|
||||
|
||||
field_data = []
|
||||
for field in include:
|
||||
field_val = getattr(self, field)
|
||||
serialization_func = serialization_map.get(
|
||||
type(field_val), lambda _k, _v: None
|
||||
)
|
||||
serialized = serialization_func(
|
||||
f"{key_root}{level_delimiter}{field}", field_val
|
||||
)
|
||||
if serialized is not None:
|
||||
field_data.append(serialized)
|
||||
|
||||
return ",".join(field_data)
|
||||
|
||||
@@ -26,9 +26,11 @@ class MinerMake(str, Enum):
|
||||
AURADINE = "Auradine"
|
||||
EPIC = "ePIC"
|
||||
BITAXE = "BitAxe"
|
||||
LUCKYMINER = "LuckyMiner"
|
||||
ICERIVER = "IceRiver"
|
||||
HAMMER = "Hammer"
|
||||
VOLCMINER = "VolcMiner"
|
||||
ELPHAPEX = "Elphapex"
|
||||
BRAIINS = "Braiins"
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -58,7 +58,9 @@ class AntminerModels(MinerModelType):
|
||||
S19jXP = "S19j XP"
|
||||
T19 = "T19"
|
||||
S21 = "S21"
|
||||
S21Plus = "S21+"
|
||||
S21Pro = "S21 Pro"
|
||||
S21Hydro = "S21 Hydro"
|
||||
T21 = "T21"
|
||||
|
||||
def __str__(self):
|
||||
@@ -447,6 +449,7 @@ class AvalonminerModels(MinerModelType):
|
||||
Avalon1166Pro = "Avalon 1166 Pro"
|
||||
Avalon1126Pro = "Avalon 1126 Pro"
|
||||
Avalon1246 = "Avalon 1246"
|
||||
Avalon1566 = "Avalon 1566"
|
||||
AvalonNano3 = "Avalon Nano 3"
|
||||
|
||||
def __str__(self):
|
||||
@@ -478,7 +481,9 @@ class GoldshellModels(MinerModelType):
|
||||
class ePICModels(MinerModelType):
|
||||
BM520i = "BlockMiner 520i"
|
||||
BM720i = "BlockMiner 720i"
|
||||
eLITE1 = "BlockMiner eLITE 1.0"
|
||||
S19jProDual = "S19j Pro Dual"
|
||||
S19kProDual = "S19k Pro Dual"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
@@ -507,6 +512,14 @@ class BitAxeModels(MinerModelType):
|
||||
return self.value
|
||||
|
||||
|
||||
class LuckyMinerModels(MinerModelType):
|
||||
LV07 = "LV07"
|
||||
LV08 = "LV08"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class IceRiverModels(MinerModelType):
|
||||
KS0 = "KS0"
|
||||
KS1 = "KS1"
|
||||
@@ -541,6 +554,10 @@ class BraiinsModels(MinerModelType):
|
||||
BMM101 = "BMM101"
|
||||
|
||||
|
||||
class ElphapexModels(MinerModelType):
|
||||
DG1Plus = "DG1+"
|
||||
|
||||
|
||||
class MinerModel:
|
||||
ANTMINER = AntminerModels
|
||||
WHATSMINER = WhatsminerModels
|
||||
@@ -550,7 +567,9 @@ class MinerModel:
|
||||
AURADINE = AuradineModels
|
||||
EPIC = ePICModels
|
||||
BITAXE = BitAxeModels
|
||||
LUCKYMINER = LuckyMinerModels
|
||||
ICERIVER = IceRiverModels
|
||||
HAMMER = HammerModels
|
||||
VOLCMINER = VolcMinerModels
|
||||
ELPHAPEX = ElphapexModels
|
||||
BRAIINS = BraiinsModels
|
||||
|
||||
@@ -21,4 +21,5 @@ from .epic import *
|
||||
from .hiveon import *
|
||||
from .luxos import *
|
||||
from .marathon import *
|
||||
from .mskminer import *
|
||||
from .vnish import *
|
||||
|
||||
@@ -15,12 +15,20 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AntminerModern
|
||||
from pyasic.miners.device.models import S21, S21Pro
|
||||
from pyasic.miners.device.models import S21, S21Hydro, S21Plus, S21Pro
|
||||
|
||||
|
||||
class BMMinerS21(AntminerModern, S21):
|
||||
pass
|
||||
|
||||
|
||||
class BMMinerS21Plus(AntminerModern, S21Plus):
|
||||
pass
|
||||
|
||||
|
||||
class BMMinerS21Pro(AntminerModern, S21Pro):
|
||||
pass
|
||||
|
||||
|
||||
class BMMinerS21Hydro(AntminerModern, S21Hydro):
|
||||
pass
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from .S21 import BMMinerS21, BMMinerS21Pro
|
||||
from .S21 import BMMinerS21, BMMinerS21Hydro, BMMinerS21Plus, BMMinerS21Pro
|
||||
from .T21 import BMMinerT21
|
||||
|
||||
@@ -15,8 +15,12 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import BOSer
|
||||
from pyasic.miners.device.models import S21
|
||||
from pyasic.miners.device.models import S21, S21Pro
|
||||
|
||||
|
||||
class BOSMinerS21(BOSer, S21):
|
||||
pass
|
||||
|
||||
|
||||
class BOSMinerS21Pro(BOSer, S21Pro):
|
||||
pass
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .S21 import BOSMinerS21
|
||||
from .S21 import BOSMinerS21, BOSMinerS21Pro
|
||||
from .T21 import BOSMinerT21
|
||||
|
||||
@@ -59,3 +59,9 @@ class ePICS19jProDual(ePIC, S19jPro):
|
||||
raw_model = MinerModel.EPIC.S19jProDual
|
||||
expected_fans = S19jPro.expected_fans * 2
|
||||
expected_hashboards = S19jPro.expected_hashboards * 2
|
||||
|
||||
|
||||
class ePICS19kProDual(ePIC, S19kPro):
|
||||
raw_model = MinerModel.EPIC.S19kProDual
|
||||
expected_fans = S19kPro.expected_fans * 2
|
||||
expected_hashboards = S19kPro.expected_hashboards * 2
|
||||
|
||||
@@ -21,6 +21,7 @@ from .S19 import (
|
||||
ePICS19jProDual,
|
||||
ePICS19jProPlus,
|
||||
ePICS19kPro,
|
||||
ePICS19kProDual,
|
||||
ePICS19Pro,
|
||||
ePICS19XP,
|
||||
)
|
||||
|
||||
@@ -27,6 +27,7 @@ from pyasic.miners.device.models import (
|
||||
S19jNoPIC,
|
||||
S19jPro,
|
||||
S19KPro,
|
||||
S19NoPIC,
|
||||
S19Plus,
|
||||
S19Pro,
|
||||
S19ProHydro,
|
||||
@@ -39,6 +40,10 @@ class HiveonS19(HiveonModern, S19):
|
||||
pass
|
||||
|
||||
|
||||
class HiveonS19NoPIC(HiveonModern, S19NoPIC):
|
||||
pass
|
||||
|
||||
|
||||
class HiveonS19Plus(HiveonModern, S19Plus):
|
||||
pass
|
||||
|
||||
@@ -95,5 +100,5 @@ class HiveonS19ProPlusHydro(HiveonModern, S19ProPlusHydro):
|
||||
pass
|
||||
|
||||
|
||||
class HiveonS19KPro(HiveonModern, S19KPro):
|
||||
class HiveonS19kPro(HiveonModern, S19KPro):
|
||||
pass
|
||||
|
||||
@@ -23,8 +23,9 @@ from .S19 import (
|
||||
HiveonS19j,
|
||||
HiveonS19jNoPIC,
|
||||
HiveonS19jPro,
|
||||
HiveonS19KPro,
|
||||
HiveonS19kPro,
|
||||
HiveonS19L,
|
||||
HiveonS19NoPIC,
|
||||
HiveonS19Plus,
|
||||
HiveonS19Pro,
|
||||
HiveonS19ProHydro,
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
import asyncssh
|
||||
|
||||
from pyasic.data import HashBoard
|
||||
from pyasic.device.algorithm import AlgoHashRate, HashUnit
|
||||
from pyasic.errors import APIError
|
||||
|
||||
24
pyasic/miners/antminer/mskminer/X19/S19.py
Normal file
24
pyasic/miners/antminer/mskminer/X19/S19.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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.mskminer import MSKMiner
|
||||
from pyasic.miners.device.models import (
|
||||
S19NoPIC,
|
||||
)
|
||||
|
||||
|
||||
class MSKMinerS19NoPIC(MSKMiner, S19NoPIC):
|
||||
pass
|
||||
17
pyasic/miners/antminer/mskminer/X19/__init__.py
Normal file
17
pyasic/miners/antminer/mskminer/X19/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .S19 import MSKMinerS19NoPIC
|
||||
17
pyasic/miners/antminer/mskminer/__init__.py
Normal file
17
pyasic/miners/antminer/mskminer/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .X19 import *
|
||||
@@ -20,6 +20,7 @@ from pyasic.miners.device.models import (
|
||||
S19XP,
|
||||
S19a,
|
||||
S19aPro,
|
||||
S19i,
|
||||
S19j,
|
||||
S19jPro,
|
||||
S19kPro,
|
||||
@@ -53,6 +54,10 @@ class VNishS19aPro(VNish, S19aPro):
|
||||
pass
|
||||
|
||||
|
||||
class VNishS19i(VNish, S19i):
|
||||
pass
|
||||
|
||||
|
||||
class VNishS19j(VNish, S19j):
|
||||
pass
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from .S19 import (
|
||||
VNishS19,
|
||||
VNishS19a,
|
||||
VNishS19aPro,
|
||||
VNishS19i,
|
||||
VNishS19j,
|
||||
VNishS19jPro,
|
||||
VNishS19kPro,
|
||||
|
||||
22
pyasic/miners/avalonminer/cgminer/A15X/A1566.py
Normal file
22
pyasic/miners/avalonminer/cgminer/A15X/A1566.py
Normal 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 Avalon1566
|
||||
|
||||
|
||||
class CGMinerAvalon1566(AvalonMiner, Avalon1566):
|
||||
pass
|
||||
17
pyasic/miners/avalonminer/cgminer/A15X/__init__.py
Normal file
17
pyasic/miners/avalonminer/cgminer/A15X/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .A1566 import CGMinerAvalon1566
|
||||
@@ -20,4 +20,5 @@ from .A9X import *
|
||||
from .A10X import *
|
||||
from .A11X import *
|
||||
from .A12X import *
|
||||
from .A15X import *
|
||||
from .nano import *
|
||||
|
||||
@@ -13,10 +13,95 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from typing import Optional
|
||||
|
||||
from pyasic import APIError
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.miners.device.models import AvalonNano3
|
||||
from pyasic.web.avalonminer import AvalonMinerWebAPI
|
||||
|
||||
AVALON_NANO_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac",
|
||||
[WebAPICommand("web_minerinfo", "get_minerinfo")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("rpc_devs", "devs")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||
"_get_env_temp",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
"_get_wattage_limit",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||
"_get_fault_light",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools",
|
||||
[RPCAPICommand("rpc_pools", "pools")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class CGMinerAvalonNano3(AvalonMiner, AvalonNano3):
|
||||
pass
|
||||
_web_cls = AvalonMinerWebAPI
|
||||
web: AvalonMinerWebAPI
|
||||
|
||||
data_locations = AVALON_NANO_DATA_LOC
|
||||
|
||||
async def _get_mac(self, web_minerinfo: dict) -> Optional[dict]:
|
||||
if web_minerinfo is None:
|
||||
try:
|
||||
web_minerinfo = await self.web.minerinfo()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_minerinfo is not None:
|
||||
try:
|
||||
mac = web_minerinfo.get("mac")
|
||||
if mac is not None:
|
||||
return mac.upper()
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
|
||||
@@ -22,14 +22,17 @@ from .bmminer import BMMiner
|
||||
from .braiins_os import BOSer, BOSMiner
|
||||
from .btminer import BTMiner
|
||||
from .cgminer import CGMiner
|
||||
from .elphapex import ElphapexMiner
|
||||
from .epic import ePIC
|
||||
from .goldshell import GoldshellMiner
|
||||
from .hammer import BlackMiner
|
||||
from .hiveon import HiveonModern, HiveonOld
|
||||
from .iceriver import IceRiver
|
||||
from .innosilicon import Innosilicon
|
||||
from .luckyminer import LuckyMiner
|
||||
from .luxminer import LUXMiner
|
||||
from .marathon import MaraMiner
|
||||
from .mskminer import MSKMiner
|
||||
from .unknown import UnknownMiner
|
||||
from .vnish import VNish
|
||||
from .whatsminer import M2X, M3X, M5X, M6X, M7X
|
||||
|
||||
@@ -75,6 +75,10 @@ ANTMINER_MODERN_DATA_LOC = DataLocations(
|
||||
"_get_fault_light",
|
||||
[WebAPICommand("web_get_blink_status", "get_blink_status")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[],
|
||||
),
|
||||
str(DataOptions.IS_MINING): DataFunction(
|
||||
"_is_mining",
|
||||
[WebAPICommand("web_get_conf", "get_miner_conf")],
|
||||
@@ -248,6 +252,9 @@ class AntminerModern(BMMiner):
|
||||
return errors
|
||||
|
||||
async def _get_hashboards(self) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
hashboards = [
|
||||
HashBoard(slot=idx, expected_chips=self.expected_chips)
|
||||
for idx in range(self.expected_hashboards)
|
||||
@@ -561,6 +568,9 @@ class AntminerOld(CGMiner):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
@@ -589,6 +599,8 @@ class AntminerOld(CGMiner):
|
||||
return fans_data
|
||||
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
hashboards = []
|
||||
|
||||
if rpc_stats is None:
|
||||
|
||||
@@ -303,6 +303,9 @@ class Auradine(StockFirmware):
|
||||
async def _get_hashboards(
|
||||
self, rpc_devs: dict = None, web_ipreport: dict = None
|
||||
) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
@@ -382,6 +385,9 @@ class Auradine(StockFirmware):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, web_fan: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if web_fan is None:
|
||||
try:
|
||||
web_fan = await self.web.get_fan()
|
||||
|
||||
@@ -57,6 +57,10 @@ AVALON_DATA_LOC = DataLocations(
|
||||
"_get_wattage_limit",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
@@ -100,6 +104,23 @@ class AvalonMiner(CGMiner):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
try:
|
||||
if wattage < 3:
|
||||
limit = wattage
|
||||
elif wattage > 100:
|
||||
limit = 2
|
||||
elif wattage > 80:
|
||||
limit = 1
|
||||
else:
|
||||
limit = 0
|
||||
data = await self.rpc.ascset(0, "worklevel,set", 1)
|
||||
except APIError:
|
||||
return False
|
||||
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
||||
return True
|
||||
return False
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
try:
|
||||
data = await self.rpc.restart()
|
||||
@@ -191,6 +212,9 @@ class AvalonMiner(CGMiner):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
@@ -289,7 +313,25 @@ class AvalonMiner(CGMiner):
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
return int(parsed_stats["WALLPOWER"][0])
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
|
||||
@@ -129,6 +129,9 @@ class BFGMiner(StockFirmware):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
hashboards = []
|
||||
|
||||
if rpc_stats is None:
|
||||
@@ -185,6 +188,9 @@ class BFGMiner(StockFirmware):
|
||||
return hashboards
|
||||
|
||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
|
||||
@@ -1,235 +1,7 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic import APIError, MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.device.algorithm import AlgoHashRate
|
||||
from pyasic.device.firmware import MinerFirmware
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||
from pyasic.web.bitaxe import BitAxeWebAPI
|
||||
|
||||
BITAXE_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
}
|
||||
)
|
||||
from pyasic.miners.backends.espminer import ESPMiner
|
||||
|
||||
|
||||
class BitAxe(BaseMiner):
|
||||
class BitAxe(ESPMiner):
|
||||
"""Handler for BitAxe"""
|
||||
|
||||
web: BitAxeWebAPI
|
||||
_web_cls = BitAxeWebAPI
|
||||
|
||||
firmware = MinerFirmware.STOCK
|
||||
|
||||
data_locations = BITAXE_DATA_LOC
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
await self.web.restart()
|
||||
return True
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
web_system_info = await self.web.system_info()
|
||||
return MinerConfig.from_bitaxe(web_system_info)
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
await self.web.update_settings(**config.as_bitaxe())
|
||||
|
||||
async def _get_wattage(self, web_system_info: dict = None) -> Optional[int]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return round(web_system_info["power"])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(
|
||||
self, web_system_info: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return self.algo.hashrate(
|
||||
rate=float(web_system_info["hashRate"]), unit=self.algo.unit.GH
|
||||
).into(self.algo.unit.default)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_expected_hashrate(
|
||||
self, web_system_info: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
expected_hashrate = (
|
||||
web_system_info.get("smallCoreCount")
|
||||
* web_system_info.get("asicCount")
|
||||
* web_system_info.get("frequency")
|
||||
)
|
||||
|
||||
return self.algo.hashrate(
|
||||
rate=float(expected_hashrate), unit=self.algo.unit.MH
|
||||
).into(self.algo.unit.default)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, web_system_info: dict = None) -> Optional[int]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["uptimeSeconds"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, web_system_info: dict = None) -> List[HashBoard]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return [
|
||||
HashBoard(
|
||||
hashrate=self.algo.hashrate(
|
||||
rate=float(web_system_info["hashRate"]),
|
||||
unit=self.algo.unit.GH,
|
||||
).into(self.algo.unit.default),
|
||||
chip_temp=web_system_info.get("temp"),
|
||||
temp=web_system_info.get("vrTemp"),
|
||||
chips=web_system_info.get("asicCount", 1),
|
||||
expected_chips=self.expected_chips,
|
||||
missing=False,
|
||||
active=True,
|
||||
voltage=web_system_info.get("voltage"),
|
||||
)
|
||||
]
|
||||
except KeyError:
|
||||
pass
|
||||
return []
|
||||
|
||||
async def _get_fans(self, web_system_info: dict = None) -> List[Fan]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return [Fan(speed=web_system_info["fanrpm"])]
|
||||
except KeyError:
|
||||
pass
|
||||
return []
|
||||
|
||||
async def _get_hostname(self, web_system_info: dict = None) -> Optional[str]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["hostname"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_api_ver(self, web_system_info: dict = None) -> Optional[str]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["version"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_fw_ver(self, web_system_info: dict = None) -> Optional[str]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["version"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_mac(self, web_system_info: dict = None) -> Optional[str]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["macAddr"].upper()
|
||||
except KeyError:
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -133,6 +133,9 @@ class BMMiner(StockFirmware):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
hashboards = []
|
||||
|
||||
if rpc_stats is None:
|
||||
@@ -202,6 +205,9 @@ class BMMiner(StockFirmware):
|
||||
return hashboards
|
||||
|
||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
|
||||
@@ -376,6 +376,9 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
rpc_devdetails: dict = None,
|
||||
rpc_devs: dict = None,
|
||||
) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
@@ -473,6 +476,9 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, rpc_fans: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if rpc_fans is None:
|
||||
try:
|
||||
rpc_fans = await self.rpc.fans()
|
||||
@@ -926,6 +932,9 @@ class BOSer(BraiinsOSFirmware):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, grpc_hashboards: dict = None) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
@@ -997,6 +1006,9 @@ class BOSer(BraiinsOSFirmware):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if grpc_cooling_state is None:
|
||||
try:
|
||||
grpc_cooling_state = await self.web.get_cooling_state()
|
||||
|
||||
@@ -412,6 +412,9 @@ class BTMiner(StockFirmware):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, rpc_devs: dict = None) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
@@ -490,6 +493,9 @@ class BTMiner(StockFirmware):
|
||||
async def _get_fans(
|
||||
self, rpc_summary: dict = None, rpc_get_psu: dict = None
|
||||
) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
rpc_summary = await self.rpc.summary()
|
||||
|
||||
380
pyasic/miners/backends/elphapex.py
Normal file
380
pyasic/miners/backends/elphapex.py
Normal file
@@ -0,0 +1,380 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic import APIError, MinerConfig
|
||||
from pyasic.data import Fan, HashBoard, X19Error
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
from pyasic.device.algorithm import AlgoHashRate
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.miners.device.firmware import StockFirmware
|
||||
from pyasic.web.elphapex import ElphapexWebAPI
|
||||
|
||||
ELPHAPEX_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac",
|
||||
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname",
|
||||
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[WebAPICommand("web_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[WebAPICommand("web_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[WebAPICommand("web_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.ERRORS): DataFunction(
|
||||
"_get_errors",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||
"_get_fault_light",
|
||||
[WebAPICommand("web_get_blink_status", "get_blink_status")],
|
||||
),
|
||||
str(DataOptions.IS_MINING): DataFunction(
|
||||
"_is_mining",
|
||||
[WebAPICommand("web_get_miner_conf", "get_miner_conf")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools",
|
||||
[WebAPICommand("web_pools", "pools")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ElphapexMiner(StockFirmware):
|
||||
"""Handler for Elphapex miners."""
|
||||
|
||||
_web_cls = ElphapexWebAPI
|
||||
web: ElphapexWebAPI
|
||||
|
||||
data_locations = ELPHAPEX_DATA_LOC
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
data = await self.web.get_miner_conf()
|
||||
if data:
|
||||
self.config = MinerConfig.from_elphapex(data)
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
self.config = config
|
||||
await self.web.set_miner_conf(config.as_elphapex(user_suffix=user_suffix))
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
data = await self.web.blink(blink=True)
|
||||
if data:
|
||||
if data.get("code") == "B000":
|
||||
self.light = True
|
||||
return self.light
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
data = await self.web.blink(blink=False)
|
||||
if data:
|
||||
if data.get("code") == "B100":
|
||||
self.light = False
|
||||
return self.light
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
data = await self.web.reboot()
|
||||
if data:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def _get_api_ver(self, web_summary: dict = None) -> Optional[str]:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary is not None:
|
||||
try:
|
||||
self.api_ver = web_summary["STATUS"]["api_version"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(self, web_get_system_info: dict = None) -> Optional[str]:
|
||||
if web_get_system_info is None:
|
||||
try:
|
||||
web_get_system_info = await self.web.get_system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_system_info is not None:
|
||||
try:
|
||||
self.fw_ver = (
|
||||
web_get_system_info["system_filesystem_version"]
|
||||
.upper()
|
||||
.split("V")[-1]
|
||||
)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
|
||||
if web_get_system_info is None:
|
||||
try:
|
||||
web_get_system_info = await self.web.get_system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_system_info is not None:
|
||||
try:
|
||||
return web_get_system_info["hostname"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_mac(self, web_get_system_info: dict = None) -> Optional[str]:
|
||||
if web_get_system_info is None:
|
||||
try:
|
||||
web_get_system_info = await self.web.get_system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_system_info is not None:
|
||||
try:
|
||||
return web_get_system_info["macaddr"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
data = await self.web.get_network_info()
|
||||
if data:
|
||||
return data["macaddr"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
errors = []
|
||||
if web_summary is not None:
|
||||
try:
|
||||
for item in web_summary["SUMMARY"][0]["status"]:
|
||||
try:
|
||||
if not item["status"] == "s":
|
||||
errors.append(X19Error(error_message=item["msg"]))
|
||||
except KeyError:
|
||||
continue
|
||||
except LookupError:
|
||||
pass
|
||||
return errors
|
||||
|
||||
async def _get_hashboards(self, web_stats: dict | None = None) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
hashboards = [
|
||||
HashBoard(slot=idx, expected_chips=self.expected_chips)
|
||||
for idx in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
if web_stats is None:
|
||||
try:
|
||||
web_stats = await self.web.stats()
|
||||
except APIError:
|
||||
return hashboards
|
||||
|
||||
if web_stats is not None:
|
||||
try:
|
||||
for board in web_stats["STATS"][0]["chain"]:
|
||||
hashboards[board["index"]].hashrate = self.algo.hashrate(
|
||||
rate=board["rate_real"], unit=self.algo.unit.MH
|
||||
).into(self.algo.unit.default)
|
||||
hashboards[board["index"]].chips = board["asic_num"]
|
||||
board_temp_data = list(
|
||||
filter(lambda x: not x == 0, board["temp_pcb"])
|
||||
)
|
||||
hashboards[board["index"]].temp = sum(board_temp_data) / len(
|
||||
board_temp_data
|
||||
)
|
||||
chip_temp_data = list(
|
||||
filter(lambda x: not x == "", board["temp_chip"])
|
||||
)
|
||||
hashboards[board["index"]].chip_temp = sum(
|
||||
[int(i) / 1000 for i in chip_temp_data]
|
||||
) / len(chip_temp_data)
|
||||
hashboards[board["index"]].serial_number = board["sn"]
|
||||
hashboards[board["index"]].missing = False
|
||||
except LookupError:
|
||||
pass
|
||||
return hashboards
|
||||
|
||||
async def _get_fault_light(
|
||||
self, web_get_blink_status: dict = None
|
||||
) -> Optional[bool]:
|
||||
if self.light:
|
||||
return self.light
|
||||
|
||||
if web_get_blink_status is None:
|
||||
try:
|
||||
web_get_blink_status = await self.web.get_blink_status()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_blink_status is not None:
|
||||
try:
|
||||
self.light = web_get_blink_status["blink"]
|
||||
except KeyError:
|
||||
pass
|
||||
return self.light
|
||||
|
||||
async def _get_expected_hashrate(
|
||||
self, web_stats: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if web_stats is None:
|
||||
try:
|
||||
web_stats = await self.web.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_stats is not None:
|
||||
try:
|
||||
expected_rate = web_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
rate_unit = web_stats["STATS"][1]["rate_unit"]
|
||||
except KeyError:
|
||||
rate_unit = "MH"
|
||||
return self.algo.hashrate(
|
||||
rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _is_mining(self, web_get_miner_conf: dict = None) -> Optional[bool]:
|
||||
if web_get_miner_conf is None:
|
||||
try:
|
||||
web_get_miner_conf = await self.web.get_miner_conf()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_miner_conf is not None:
|
||||
try:
|
||||
if str(web_get_miner_conf["fc-work-mode"]).isdigit():
|
||||
return (
|
||||
False if int(web_get_miner_conf["fc-work-mode"]) == 1 else True
|
||||
)
|
||||
return False
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, web_summary: dict = None) -> Optional[int]:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary is not None:
|
||||
try:
|
||||
return int(web_summary["SUMMARY"][1]["elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_fans(self, web_stats: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if web_stats is None:
|
||||
try:
|
||||
web_stats = await self.web.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans = [Fan() for _ in range(self.expected_fans)]
|
||||
if web_stats is not None:
|
||||
for fan_n in range(self.expected_fans):
|
||||
try:
|
||||
fans[fan_n].speed = int(web_stats["STATS"][0]["fan"][fan_n])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
return fans
|
||||
|
||||
async def _get_pools(self, web_pools: list = None) -> List[PoolMetrics]:
|
||||
if web_pools is None:
|
||||
try:
|
||||
web_pools = await self.web.pools()
|
||||
except APIError:
|
||||
return []
|
||||
|
||||
active_pool_index = None
|
||||
highest_priority = float("inf")
|
||||
|
||||
for pool_info in web_pools["POOLS"]:
|
||||
if (
|
||||
pool_info.get("status") == "Alive"
|
||||
and pool_info.get("priority", float("inf")) < highest_priority
|
||||
):
|
||||
highest_priority = pool_info["priority"]
|
||||
active_pool_index = pool_info["index"]
|
||||
|
||||
pools_data = []
|
||||
if web_pools is not None:
|
||||
try:
|
||||
for pool_info in web_pools["POOLS"]:
|
||||
url = pool_info.get("url")
|
||||
pool_url = PoolUrl.from_str(url) if url else None
|
||||
pool_data = PoolMetrics(
|
||||
accepted=pool_info.get("accepted"),
|
||||
rejected=pool_info.get("rejected"),
|
||||
get_failures=pool_info.get("stale"),
|
||||
remote_failures=pool_info.get("discarded"),
|
||||
active=pool_info.get("index") == active_pool_index,
|
||||
alive=pool_info.get("status") == "Alive",
|
||||
url=pool_url,
|
||||
user=pool_info.get("user"),
|
||||
index=pool_info.get("index"),
|
||||
)
|
||||
pools_data.append(pool_data)
|
||||
except LookupError:
|
||||
pass
|
||||
return pools_data
|
||||
@@ -21,7 +21,7 @@ from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
from pyasic.device.algorithm import AlgoHashRate
|
||||
from pyasic.device.algorithm import AlgoHashRate, ScryptAlgo
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.logger import logger
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||
@@ -138,6 +138,8 @@ class ePIC(ePICFirmware):
|
||||
await self.web.set_ptune_algo(conf["ptune"])
|
||||
|
||||
## Pools
|
||||
if self.algo == ScryptAlgo:
|
||||
conf["pools"]["coin"] = "Ltc"
|
||||
await self.web.set_pools(conf["pools"])
|
||||
except APIError:
|
||||
pass
|
||||
@@ -283,6 +285,9 @@ class ePIC(ePICFirmware):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, web_summary: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
@@ -302,6 +307,9 @@ class ePIC(ePICFirmware):
|
||||
async def _get_hashboards(
|
||||
self, web_summary: dict = None, web_capabilities: dict = None
|
||||
) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
|
||||
241
pyasic/miners/backends/espminer.py
Normal file
241
pyasic/miners/backends/espminer.py
Normal file
@@ -0,0 +1,241 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic import APIError, MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.device.algorithm import AlgoHashRate
|
||||
from pyasic.device.firmware import MinerFirmware
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||
from pyasic.web.espminer import ESPMinerWebAPI
|
||||
|
||||
ESPMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ESPMiner(BaseMiner):
|
||||
"""Handler for ESPMiner"""
|
||||
|
||||
web: ESPMinerWebAPI
|
||||
_web_cls = ESPMinerWebAPI
|
||||
|
||||
firmware = MinerFirmware.STOCK
|
||||
|
||||
data_locations = ESPMINER_DATA_LOC
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
await self.web.restart()
|
||||
return True
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
web_system_info = await self.web.system_info()
|
||||
return MinerConfig.from_espminer(web_system_info)
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
await self.web.update_settings(**config.as_espminer())
|
||||
|
||||
async def _get_wattage(self, web_system_info: dict = None) -> Optional[int]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return round(web_system_info["power"])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(
|
||||
self, web_system_info: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return self.algo.hashrate(
|
||||
rate=float(web_system_info["hashRate"]), unit=self.algo.unit.GH
|
||||
).into(self.algo.unit.default)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_expected_hashrate(
|
||||
self, web_system_info: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
expected_hashrate = (
|
||||
web_system_info.get("smallCoreCount")
|
||||
* web_system_info.get("asicCount")
|
||||
* web_system_info.get("frequency")
|
||||
)
|
||||
|
||||
return self.algo.hashrate(
|
||||
rate=float(expected_hashrate), unit=self.algo.unit.MH
|
||||
).into(self.algo.unit.default)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, web_system_info: dict = None) -> Optional[int]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["uptimeSeconds"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, web_system_info: dict = None) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return [
|
||||
HashBoard(
|
||||
hashrate=self.algo.hashrate(
|
||||
rate=float(web_system_info["hashRate"]),
|
||||
unit=self.algo.unit.GH,
|
||||
).into(self.algo.unit.default),
|
||||
chip_temp=web_system_info.get("temp"),
|
||||
temp=web_system_info.get("vrTemp"),
|
||||
chips=web_system_info.get("asicCount", 1),
|
||||
expected_chips=self.expected_chips,
|
||||
missing=False,
|
||||
active=True,
|
||||
voltage=web_system_info.get("voltage"),
|
||||
)
|
||||
]
|
||||
except KeyError:
|
||||
pass
|
||||
return []
|
||||
|
||||
async def _get_fans(self, web_system_info: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return [Fan(speed=web_system_info["fanrpm"])]
|
||||
except KeyError:
|
||||
pass
|
||||
return []
|
||||
|
||||
async def _get_hostname(self, web_system_info: dict = None) -> Optional[str]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["hostname"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_api_ver(self, web_system_info: dict = None) -> Optional[str]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["version"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_fw_ver(self, web_system_info: dict = None) -> Optional[str]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["version"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_mac(self, web_system_info: dict = None) -> Optional[str]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["macAddr"].upper()
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -145,6 +145,9 @@ class GoldshellMiner(BFGMiner):
|
||||
async def _get_hashboards(
|
||||
self, rpc_devs: dict = None, rpc_devdetails: dict = None
|
||||
) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
if rpc_devs is None:
|
||||
try:
|
||||
rpc_devs = await self.rpc.devs()
|
||||
|
||||
@@ -180,6 +180,9 @@ class BlackMiner(StockFirmware):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
hashboards = []
|
||||
|
||||
if rpc_stats is None:
|
||||
@@ -245,6 +248,9 @@ class BlackMiner(StockFirmware):
|
||||
return hashboards
|
||||
|
||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
|
||||
@@ -79,6 +79,9 @@ class IceRiver(StockFirmware):
|
||||
return MinerConfig.from_iceriver(web_userpanel)
|
||||
|
||||
async def _get_fans(self, web_userpanel: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if web_userpanel is None:
|
||||
try:
|
||||
web_userpanel = await self.web.userpanel()
|
||||
@@ -170,6 +173,9 @@ class IceRiver(StockFirmware):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, web_userpanel: dict = None) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
if web_userpanel is None:
|
||||
try:
|
||||
web_userpanel = await self.web.userpanel()
|
||||
|
||||
@@ -211,6 +211,9 @@ class Innosilicon(CGMiner):
|
||||
async def _get_hashboards(
|
||||
self, rpc_stats: dict = None, web_get_all: dict = None
|
||||
) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
if web_get_all:
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
@@ -304,6 +307,9 @@ class Innosilicon(CGMiner):
|
||||
return wattage
|
||||
|
||||
async def _get_fans(self, web_get_all: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if web_get_all:
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
|
||||
7
pyasic/miners/backends/luckyminer.py
Normal file
7
pyasic/miners/backends/luckyminer.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from pyasic.miners.backends.espminer import ESPMiner
|
||||
|
||||
|
||||
class LuckyMiner(ESPMiner):
|
||||
"""Handler for LuckyMiner"""
|
||||
|
||||
pass
|
||||
@@ -17,6 +17,7 @@ import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.config.mining import MiningModePreset
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
from pyasic.device.algorithm import AlgoHashRate
|
||||
@@ -47,6 +48,13 @@ LUXMINER_DATA_LOC = DataLocations(
|
||||
"_get_wattage",
|
||||
[RPCAPICommand("rpc_power", "power")],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
"_get_wattage_limit",
|
||||
[
|
||||
RPCAPICommand("rpc_config", "config"),
|
||||
RPCAPICommand("rpc_profiles", "profiles"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("rpc_fans", "fans")],
|
||||
@@ -144,14 +152,14 @@ class LUXMiner(LuxOSFirmware):
|
||||
rpc_profiles=data.get("profiles", [{}])[0],
|
||||
)
|
||||
|
||||
async def upgrade_firmware(self) -> bool:
|
||||
async def upgrade_firmware(self, *args, **kwargs) -> bool:
|
||||
"""
|
||||
Upgrade the firmware on a LuxOS miner by calling the 'updaterun' API command.
|
||||
Returns:
|
||||
bool: True if the firmware upgrade was successfully initiated, False otherwise.
|
||||
"""
|
||||
try:
|
||||
await self.rpc.upgraderun()
|
||||
await self.rpc.updaterun()
|
||||
logging.info(f"{self.ip}: Firmware upgrade initiated successfully.")
|
||||
return True
|
||||
|
||||
@@ -232,6 +240,9 @@ class LUXMiner(LuxOSFirmware):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
hashboards = [
|
||||
HashBoard(slot=idx, expected_chips=self.expected_chips)
|
||||
for idx in range(self.expected_hashboards)
|
||||
@@ -289,7 +300,21 @@ class LUXMiner(LuxOSFirmware):
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_wattage_limit(
|
||||
self, rpc_config: dict = None, rpc_profiles: list[dict] = None
|
||||
) -> Optional[int]:
|
||||
try:
|
||||
active_preset = MiningModePreset.get_active_preset_from_luxos(
|
||||
rpc_config, rpc_profiles
|
||||
)
|
||||
return active_preset.power
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, rpc_fans: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if rpc_fans is None:
|
||||
try:
|
||||
rpc_fans = await self.rpc.fans()
|
||||
|
||||
@@ -164,6 +164,9 @@ class MaraMiner(MaraFirmware):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, web_hashboards: dict = None) -> List[HashBoard]:
|
||||
if self.expected_hashboards is None:
|
||||
return []
|
||||
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
@@ -250,6 +253,9 @@ class MaraMiner(MaraFirmware):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, web_fans: dict = None) -> List[Fan]:
|
||||
if self.expected_fans is None:
|
||||
return []
|
||||
|
||||
if web_fans is None:
|
||||
try:
|
||||
web_fans = await self.web.fans()
|
||||
|
||||
110
pyasic/miners/backends/mskminer.py
Normal file
110
pyasic/miners/backends/mskminer.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from typing import Optional
|
||||
|
||||
from pyasic import APIError
|
||||
from pyasic.device.algorithm import AlgoHashRate
|
||||
from pyasic.miners.backends import BMMiner
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.web.mskminer import MSKMinerWebAPI
|
||||
|
||||
MSKMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac",
|
||||
[WebAPICommand("web_info_v1", "info_v1")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools",
|
||||
[RPCAPICommand("rpc_pools", "pools")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class MSKMiner(BMMiner):
|
||||
"""Handler for MSKMiner"""
|
||||
|
||||
data_locations = MSKMINER_DATA_LOC
|
||||
|
||||
web: MSKMinerWebAPI
|
||||
_web_cls = MSKMinerWebAPI
|
||||
|
||||
async def _get_hashrate(self, rpc_stats: dict = None) -> Optional[AlgoHashRate]:
|
||||
# get hr from API
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
return self.algo.hashrate(
|
||||
rate=float(rpc_stats["STATS"][0]["total_rate"]),
|
||||
unit=self.algo.unit.GH,
|
||||
).into(self.algo.unit.default)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
return rpc_stats["STATS"][0]["total_power"]
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_mac(self, web_info_v1: dict = None) -> Optional[str]:
|
||||
if web_info_v1 is None:
|
||||
try:
|
||||
web_info_v1 = await self.web.info_v1()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_info_v1 is not None:
|
||||
try:
|
||||
return web_info_v1["network_info"]["result"]["macaddr"].upper()
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
@@ -97,7 +97,7 @@ class UnknownMiner(BaseMiner):
|
||||
return None
|
||||
|
||||
async def _get_fans(self) -> List[Fan]:
|
||||
return [Fan(), Fan(), Fan(), Fan()]
|
||||
return []
|
||||
|
||||
async def _get_fan_psu(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic import MinerConfig
|
||||
from pyasic.data.error_codes import MinerErrorData, VnishError
|
||||
from pyasic.device.algorithm import AlgoHashRate
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends.bmminer import BMMiner
|
||||
@@ -85,6 +86,10 @@ VNISH_DATA_LOC = DataLocations(
|
||||
"_get_pools",
|
||||
[RPCAPICommand("rpc_pools", "pools")],
|
||||
),
|
||||
str(DataOptions.ERRORS): DataFunction(
|
||||
"_get_errors",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -266,6 +271,24 @@ class VNish(VNishFirmware, BMMiner):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
||||
errors = []
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
return errors
|
||||
|
||||
if web_summary is not None:
|
||||
chains = web_summary.get("miner", {}).get("chains", [])
|
||||
for chain in chains:
|
||||
state = chain.get("status", {}).get("state")
|
||||
description = chain.get("status", {}).get("description", "")
|
||||
if state == "failure":
|
||||
errors.append(VnishError(error_message=description))
|
||||
|
||||
return errors
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
try:
|
||||
web_settings = await self.web.settings()
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .blockminer import ePICBlockMiner520i, ePICBlockMiner720i
|
||||
from .blockminer import *
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import ePIC
|
||||
from pyasic.miners.device.models import BlockMiner520i, BlockMiner720i
|
||||
from pyasic.miners.device.models import (
|
||||
BlockMiner520i,
|
||||
BlockMiner720i,
|
||||
BlockMinerELITE1,
|
||||
)
|
||||
|
||||
|
||||
class ePICBlockMiner520i(ePIC, BlockMiner520i):
|
||||
@@ -24,3 +28,7 @@ class ePICBlockMiner520i(ePIC, BlockMiner520i):
|
||||
|
||||
class ePICBlockMiner720i(ePIC, BlockMiner720i):
|
||||
pass
|
||||
|
||||
|
||||
class ePICBlockMinerELITE1(ePIC, BlockMinerELITE1):
|
||||
pass
|
||||
|
||||
@@ -50,6 +50,10 @@ class BitAxeMake(BaseMiner):
|
||||
make = MinerMake.BITAXE
|
||||
|
||||
|
||||
class LuckyMinerMake(BaseMiner):
|
||||
make = MinerMake.LUCKYMINER
|
||||
|
||||
|
||||
class IceRiverMake(BaseMiner):
|
||||
make = MinerMake.ICERIVER
|
||||
|
||||
@@ -64,3 +68,7 @@ class VolcMinerMake(BaseMiner):
|
||||
|
||||
class BraiinsMake(BaseMiner):
|
||||
make = MinerMake.BRAIINS
|
||||
|
||||
|
||||
class ElphapexMake(BaseMiner):
|
||||
make = MinerMake.ELPHAPEX
|
||||
|
||||
@@ -18,6 +18,7 @@ from .antminer import *
|
||||
from .auradine import *
|
||||
from .avalonminer import *
|
||||
from .braiins import *
|
||||
from .elphapex import *
|
||||
from .epic import *
|
||||
from .goldshell import *
|
||||
from .hammer import *
|
||||
|
||||
@@ -27,6 +27,15 @@ class S21(AntMinerMake):
|
||||
algo = MinerAlgo.SHA256
|
||||
|
||||
|
||||
class S21Plus(AntMinerMake):
|
||||
raw_model = MinerModel.ANTMINER.S21Plus
|
||||
|
||||
expected_chips = 55
|
||||
expected_fans = 4
|
||||
expected_hashboards = 3
|
||||
algo = MinerAlgo.SHA256
|
||||
|
||||
|
||||
class S21Pro(AntMinerMake):
|
||||
raw_model = MinerModel.ANTMINER.S21Pro
|
||||
|
||||
@@ -34,3 +43,12 @@ class S21Pro(AntMinerMake):
|
||||
expected_fans = 4
|
||||
expected_hashboards = 3
|
||||
algo = MinerAlgo.SHA256
|
||||
|
||||
|
||||
class S21Hydro(AntMinerMake):
|
||||
raw_model = MinerModel.ANTMINER.S21Hydro
|
||||
|
||||
expected_chips = 216
|
||||
expected_hashboards = 3
|
||||
expected_fans = 0
|
||||
algo = MinerAlgo.SHA256
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .S21 import S21, S21Pro
|
||||
from .S21 import S21, S21Hydro, S21Plus, S21Pro
|
||||
from .T21 import T21
|
||||
|
||||
27
pyasic/miners/device/models/avalonminer/A15X/A1566.py
Normal file
27
pyasic/miners/device/models/avalonminer/A15X/A1566.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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.device.algorithm import MinerAlgo
|
||||
from pyasic.device.models import MinerModel
|
||||
from pyasic.miners.device.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon1566(AvalonMinerMake):
|
||||
raw_model = MinerModel.AVALONMINER.Avalon1566
|
||||
|
||||
expected_chips = 160
|
||||
expected_fans = 2
|
||||
expected_hashboards = 3
|
||||
algo = MinerAlgo.SHA256
|
||||
17
pyasic/miners/device/models/avalonminer/A15X/__init__.py
Normal file
17
pyasic/miners/device/models/avalonminer/A15X/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .A1566 import Avalon1566
|
||||
@@ -20,4 +20,5 @@ from .A9X import *
|
||||
from .A10X import *
|
||||
from .A11X import *
|
||||
from .A12X import *
|
||||
from .A15X import *
|
||||
from .nano import *
|
||||
|
||||
27
pyasic/miners/device/models/elphapex/DGX/DG1.py
Normal file
27
pyasic/miners/device/models/elphapex/DGX/DG1.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2024 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.device.algorithm import MinerAlgo
|
||||
from pyasic.device.models import MinerModel
|
||||
from pyasic.miners.device.makes import ElphapexMake
|
||||
|
||||
|
||||
class DG1Plus(ElphapexMake):
|
||||
raw_model = MinerModel.ELPHAPEX.DG1Plus
|
||||
|
||||
expected_chips = 204
|
||||
expected_hashboards = 4
|
||||
expected_fans = 4
|
||||
algo = MinerAlgo.SCRYPT
|
||||
1
pyasic/miners/device/models/elphapex/DGX/__init__.py
Normal file
1
pyasic/miners/device/models/elphapex/DGX/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .DG1 import DG1Plus
|
||||
1
pyasic/miners/device/models/elphapex/__init__.py
Normal file
1
pyasic/miners/device/models/elphapex/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .DGX import *
|
||||
@@ -19,3 +19,12 @@ class BlockMiner720i(ePICMake):
|
||||
expected_fans = 4
|
||||
expected_hashboards = 3
|
||||
algo = MinerAlgo.SHA256
|
||||
|
||||
|
||||
class BlockMinerELITE1(ePICMake):
|
||||
raw_model = MinerModel.EPIC.eLITE1
|
||||
|
||||
expected_chips = 105
|
||||
expected_fans = 4
|
||||
expected_hashboards = 3
|
||||
algo = MinerAlgo.SCRYPT
|
||||
|
||||
12
pyasic/miners/device/models/luckyminer/LV/LV07.py
Normal file
12
pyasic/miners/device/models/luckyminer/LV/LV07.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from pyasic.device.algorithm import MinerAlgo
|
||||
from pyasic.device.models import MinerModel
|
||||
from pyasic.miners.device.makes import LuckyMinerMake
|
||||
|
||||
|
||||
class LV07(LuckyMinerMake):
|
||||
raw_model = MinerModel.LUCKYMINER.LV07
|
||||
|
||||
expected_hashboards = 1
|
||||
expected_chips = 1
|
||||
expected_fans = 1
|
||||
algo = MinerAlgo.SHA256
|
||||
12
pyasic/miners/device/models/luckyminer/LV/LV08.py
Normal file
12
pyasic/miners/device/models/luckyminer/LV/LV08.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from pyasic.device.algorithm import MinerAlgo
|
||||
from pyasic.device.models import MinerModel
|
||||
from pyasic.miners.device.makes import LuckyMinerMake
|
||||
|
||||
|
||||
class LV08(LuckyMinerMake):
|
||||
raw_model = MinerModel.LUCKYMINER.LV08
|
||||
|
||||
expected_hashboards = 1
|
||||
expected_chips = 1
|
||||
expected_fans = 1
|
||||
algo = MinerAlgo.SHA256
|
||||
2
pyasic/miners/device/models/luckyminer/LV/__init__.py
Normal file
2
pyasic/miners/device/models/luckyminer/LV/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .LV07 import LV07
|
||||
from .LV08 import LV08
|
||||
1
pyasic/miners/device/models/luckyminer/__init__.py
Normal file
1
pyasic/miners/device/models/luckyminer/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .LV import *
|
||||
1
pyasic/miners/elphapex/__init__.py
Normal file
1
pyasic/miners/elphapex/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .daoge import *
|
||||
6
pyasic/miners/elphapex/daoge/DGX/DG1.py
Normal file
6
pyasic/miners/elphapex/daoge/DGX/DG1.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.backends.elphapex import ElphapexMiner
|
||||
from pyasic.miners.device.models import DG1Plus
|
||||
|
||||
|
||||
class ElphapexDG1Plus(ElphapexMiner, DG1Plus):
|
||||
pass
|
||||
1
pyasic/miners/elphapex/daoge/DGX/__init__.py
Normal file
1
pyasic/miners/elphapex/daoge/DGX/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .DG1 import ElphapexDG1Plus
|
||||
1
pyasic/miners/elphapex/daoge/__init__.py
Normal file
1
pyasic/miners/elphapex/daoge/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .DGX import *
|
||||
@@ -37,10 +37,12 @@ from pyasic.miners.bitaxe import *
|
||||
from pyasic.miners.blockminer import *
|
||||
from pyasic.miners.braiins import *
|
||||
from pyasic.miners.device.makes import *
|
||||
from pyasic.miners.elphapex import *
|
||||
from pyasic.miners.goldshell import *
|
||||
from pyasic.miners.hammer import *
|
||||
from pyasic.miners.iceriver import *
|
||||
from pyasic.miners.innosilicon import *
|
||||
from pyasic.miners.luckyminer import *
|
||||
from pyasic.miners.volcminer import *
|
||||
from pyasic.miners.whatsminer import *
|
||||
|
||||
@@ -62,6 +64,9 @@ class MinerTypes(enum.Enum):
|
||||
ICERIVER = 13
|
||||
HAMMER = 14
|
||||
VOLCMINER = 15
|
||||
LUCKYMINER = 16
|
||||
ELPHAPEX = 17
|
||||
MSKMINER = 18
|
||||
|
||||
|
||||
MINER_CLASSES = {
|
||||
@@ -115,8 +120,10 @@ MINER_CLASSES = {
|
||||
"ANTMINER S21": BMMinerS21,
|
||||
"ANTMINER BHB68601": BMMinerS21, # ???
|
||||
"ANTMINER BHB68606": BMMinerS21, # ???
|
||||
"ANTMINER S21+": BMMinerS21Plus,
|
||||
"ANTMINER S21 PRO": BMMinerS21Pro,
|
||||
"ANTMINER T21": BMMinerT21,
|
||||
"ANTMINER S21 HYD.": BMMinerS21Hydro,
|
||||
},
|
||||
MinerTypes.WHATSMINER: {
|
||||
None: type("WhatsminerUnknown", (BTMiner, WhatsMinerMake), {}),
|
||||
@@ -499,6 +506,7 @@ MINER_CLASSES = {
|
||||
"AVALONMINER 1166PRO": CGMinerAvalon1166Pro,
|
||||
"AVALONMINER 1246": CGMinerAvalon1246,
|
||||
"AVALONMINER NANO3": CGMinerAvalonNano3,
|
||||
"AVALONMINER 15-194": CGMinerAvalon1566,
|
||||
},
|
||||
MinerTypes.INNOSILICON: {
|
||||
None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}),
|
||||
@@ -544,6 +552,7 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19 PRO+ HYD.": BOSMinerS19ProPlusHydro,
|
||||
"ANTMINER T19": BOSMinerT19,
|
||||
"ANTMINER S21": BOSMinerS21,
|
||||
"ANTMINER S21 PRO": BOSMinerS21Pro,
|
||||
"ANTMINER T21": BOSMinerT21,
|
||||
"BRAIINS MINI MINER BMM 100": BraiinsBMM100,
|
||||
"BRAIINS MINI MINER BMM 101": BraiinsBMM101,
|
||||
@@ -559,6 +568,7 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19NOPIC": VNishS19NoPIC,
|
||||
"ANTMINER S19 PRO": VNishS19Pro,
|
||||
"ANTMINER S19J": VNishS19j,
|
||||
"ANTMINER S19I": VNishS19i,
|
||||
"ANTMINER S19J PRO": VNishS19jPro,
|
||||
"ANTMINER S19J PRO A": VNishS19jPro,
|
||||
"ANTMINER S19J PRO BB": VNishS19jPro,
|
||||
@@ -583,14 +593,22 @@ MINER_CLASSES = {
|
||||
"ANTMINER S21 PRO": ePICS21Pro,
|
||||
"ANTMINER T21": ePICT21,
|
||||
"ANTMINER S19J PRO DUAL": ePICS19jProDual,
|
||||
"ANTMINER S19K PRO DUAL": ePICS19kProDual,
|
||||
"BLOCKMINER 520I": ePICBlockMiner520i,
|
||||
"BLOCKMINER 720I": ePICBlockMiner720i,
|
||||
"BLOCKMINER ELITE 1.0": ePICBlockMinerELITE1,
|
||||
},
|
||||
MinerTypes.HIVEON: {
|
||||
None: HiveonModern,
|
||||
"ANTMINER T9": HiveonT9,
|
||||
"ANTMINER S19JPRO": HiveonS19jPro,
|
||||
"ANTMINER S19": HiveonS19,
|
||||
"ANTMINER S19K PRO": HiveonS19kPro,
|
||||
"ANTMINER S19X88": HiveonS19NoPIC,
|
||||
},
|
||||
MinerTypes.MSKMINER: {
|
||||
None: MSKMiner,
|
||||
"S19-88": MSKMinerS19NoPIC,
|
||||
},
|
||||
MinerTypes.LUX_OS: {
|
||||
None: LUXMiner,
|
||||
@@ -633,6 +651,11 @@ MINER_CLASSES = {
|
||||
"BM1397": BitAxeMax,
|
||||
"BM1370": BitAxeGamma,
|
||||
},
|
||||
MinerTypes.LUCKYMINER: {
|
||||
None: LuckyMiner,
|
||||
"LV08": LuckyMinerLV08,
|
||||
"LV07": LuckyMinerLV07,
|
||||
},
|
||||
MinerTypes.ICERIVER: {
|
||||
None: type("IceRiverUnknown", (IceRiver, IceRiverMake), {}),
|
||||
"KS0": IceRiverKS0,
|
||||
@@ -653,6 +676,10 @@ MINER_CLASSES = {
|
||||
None: type("VolcMinerUnknown", (BlackMiner, VolcMinerMake), {}),
|
||||
"VOLCMINER D1": VolcMinerD1,
|
||||
},
|
||||
MinerTypes.ELPHAPEX: {
|
||||
None: type("ElphapexUnknown", (ElphapexMiner, ElphapexMake), {}),
|
||||
"DG1+": ElphapexDG1Plus,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -730,9 +757,11 @@ class MinerFactory:
|
||||
MinerTypes.AURADINE: self.get_miner_model_auradine,
|
||||
MinerTypes.MARATHON: self.get_miner_model_marathon,
|
||||
MinerTypes.BITAXE: self.get_miner_model_bitaxe,
|
||||
MinerTypes.LUCKYMINER: self.get_miner_model_luckyminer,
|
||||
MinerTypes.ICERIVER: self.get_miner_model_iceriver,
|
||||
MinerTypes.HAMMER: self.get_miner_model_hammer,
|
||||
MinerTypes.VOLCMINER: self.get_miner_model_volcminer,
|
||||
MinerTypes.ELPHAPEX: self.get_miner_model_elphapex,
|
||||
}
|
||||
fn = miner_model_fns.get(miner_type)
|
||||
|
||||
@@ -782,6 +811,8 @@ class MinerFactory:
|
||||
mtype = MinerTypes.MARATHON
|
||||
if mtype == MinerTypes.HAMMER:
|
||||
res = await self.get_miner_model_hammer(ip)
|
||||
if res is None:
|
||||
return MinerTypes.HAMMER
|
||||
if "HAMMER" in res.upper():
|
||||
mtype = MinerTypes.HAMMER
|
||||
else:
|
||||
@@ -814,6 +845,10 @@ class MinerFactory:
|
||||
"www-authenticate", ""
|
||||
):
|
||||
return MinerTypes.HAMMER
|
||||
if web_resp.status_code == 401 and 'realm="Daoge' in web_resp.headers.get(
|
||||
"www-authenticate", ""
|
||||
):
|
||||
return MinerTypes.ELPHAPEX
|
||||
if len(web_resp.history) > 0:
|
||||
history_resp = web_resp.history[0]
|
||||
if (
|
||||
@@ -830,6 +865,8 @@ class MinerFactory:
|
||||
return MinerTypes.ICERIVER
|
||||
if "AxeOS" in web_text:
|
||||
return MinerTypes.BITAXE
|
||||
if "Lucky miner" in web_text:
|
||||
return MinerTypes.LUCKYMINER
|
||||
if "cloud-box" in web_text:
|
||||
return MinerTypes.GOLDSHELL
|
||||
if "AnthillOS" in web_text:
|
||||
@@ -842,6 +879,8 @@ class MinerFactory:
|
||||
return MinerTypes.INNOSILICON
|
||||
if "Miner UI" in web_text:
|
||||
return MinerTypes.AURADINE
|
||||
if "<title>Antminer</title>" in web_text:
|
||||
return MinerTypes.MSKMINER
|
||||
|
||||
async def _get_miner_socket(self, ip: str) -> MinerTypes | None:
|
||||
commands = ["version", "devdetails"]
|
||||
@@ -874,6 +913,7 @@ class MinerFactory:
|
||||
await writer.drain()
|
||||
|
||||
# loop to receive all the data
|
||||
timeouts_remaining = max(1, int(settings.get("factory_get_timeout", 3)))
|
||||
while True:
|
||||
try:
|
||||
d = await asyncio.wait_for(reader.read(4096), timeout=1)
|
||||
@@ -881,7 +921,10 @@ class MinerFactory:
|
||||
break
|
||||
data += d
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
timeouts_remaining -= 1
|
||||
if not timeouts_remaining:
|
||||
logger.warning(f"{ip}: Socket ping timeout.")
|
||||
break
|
||||
except ConnectionResetError:
|
||||
return
|
||||
except asyncio.CancelledError:
|
||||
@@ -914,6 +957,8 @@ class MinerFactory:
|
||||
return MinerTypes.HIVEON
|
||||
if "KAONSU" in upper_data:
|
||||
return MinerTypes.MARATHON
|
||||
if "RWGLR" in upper_data:
|
||||
return MinerTypes.MSKMINER
|
||||
if "ANTMINER" in upper_data and "DEVDETAILS" not in upper_data:
|
||||
return MinerTypes.ANTMINER
|
||||
if (
|
||||
@@ -1129,9 +1174,9 @@ class MinerFactory:
|
||||
miner_model = sock_json_data["VERSION"][0]["PROD"].upper()
|
||||
if "-" in miner_model:
|
||||
miner_model = miner_model.split("-")[0]
|
||||
if miner_model in ["AVALONNANO", "AVALON0O"]:
|
||||
nano_subtype = sock_json_data["VERSION"][0]["MODEL"].upper()
|
||||
miner_model = f"AVALONMINER {nano_subtype}"
|
||||
if miner_model in ["AVALONNANO", "AVALON0O", "AVALONMINER 15"]:
|
||||
subtype = sock_json_data["VERSION"][0]["MODEL"].upper()
|
||||
miner_model = f"AVALONMINER {subtype}"
|
||||
return miner_model
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
@@ -1289,6 +1334,18 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_luckyminer(self, ip: str) -> str | None:
|
||||
web_json_data = await self.send_web_command(ip, "/api/system/info")
|
||||
|
||||
try:
|
||||
miner_model = web_json_data["minerModel"]
|
||||
if miner_model == "":
|
||||
return None
|
||||
|
||||
return miner_model
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_iceriver(self, ip: str) -> str | None:
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
try:
|
||||
@@ -1352,6 +1409,28 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_elphapex(self, ip: str) -> str | None:
|
||||
auth = httpx.DigestAuth(
|
||||
"root", settings.get("default_elphapex_web_password", "root")
|
||||
)
|
||||
web_json_data = await self.send_web_command(
|
||||
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
||||
)
|
||||
|
||||
try:
|
||||
miner_model = web_json_data["minertype"]
|
||||
|
||||
return miner_model
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_mskminer(self, ip: str) -> str | None:
|
||||
sock_json_data = await self.send_api_command(ip, "version")
|
||||
try:
|
||||
return sock_json_data["VERSION"][0]["Type"].split(" ")[0]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
|
||||
miner_factory = MinerFactory()
|
||||
|
||||
|
||||
1
pyasic/miners/luckyminer/__init__.py
Normal file
1
pyasic/miners/luckyminer/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .espminer import *
|
||||
6
pyasic/miners/luckyminer/espminer/LV/LV07.py
Normal file
6
pyasic/miners/luckyminer/espminer/LV/LV07.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.backends.luckyminer import LuckyMiner
|
||||
from pyasic.miners.device.models.luckyminer import LV07
|
||||
|
||||
|
||||
class LuckyMinerLV07(LuckyMiner, LV07):
|
||||
pass
|
||||
6
pyasic/miners/luckyminer/espminer/LV/LV08.py
Normal file
6
pyasic/miners/luckyminer/espminer/LV/LV08.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.backends.luckyminer import LuckyMiner
|
||||
from pyasic.miners.device.models.luckyminer import LV08
|
||||
|
||||
|
||||
class LuckyMinerLV08(LuckyMiner, LV08):
|
||||
pass
|
||||
2
pyasic/miners/luckyminer/espminer/LV/__init__.py
Normal file
2
pyasic/miners/luckyminer/espminer/LV/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .LV07 import LuckyMinerLV07
|
||||
from .LV08 import LuckyMinerLV08
|
||||
1
pyasic/miners/luckyminer/espminer/__init__.py
Normal file
1
pyasic/miners/luckyminer/espminer/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .LV import *
|
||||
@@ -574,9 +574,6 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
|
||||
Returns:
|
||||
Confirmation of logging off a session.
|
||||
</details>
|
||||
@@ -844,7 +841,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
||||
"""
|
||||
return await self.send_privileged_command("voltageset", board_n, voltage)
|
||||
|
||||
async def upgraderun(self):
|
||||
async def updaterun(self) -> dict:
|
||||
"""
|
||||
Send the 'updaterun' command to the miner.
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ _settings = { # defaults
|
||||
"default_whatsminer_rpc_password": "admin",
|
||||
"default_innosilicon_web_password": "admin",
|
||||
"default_antminer_web_password": "root",
|
||||
"default_hammer_web_password": "ltc@dog",
|
||||
"default_hammer_web_password": "root",
|
||||
"default_volcminer_web_password": "ltc@dog",
|
||||
"default_bosminer_web_password": "root",
|
||||
"default_vnish_web_password": "admin",
|
||||
@@ -40,6 +40,8 @@ _settings = { # defaults
|
||||
"default_epic_web_password": "letmein",
|
||||
"default_hive_web_password": "root",
|
||||
"default_iceriver_web_password": "12345678",
|
||||
"default_elphapex_web_password": "root",
|
||||
"default_mskminer_web_password": "root",
|
||||
"default_antminer_ssh_password": "miner",
|
||||
"default_bosminer_ssh_password": "root",
|
||||
}
|
||||
|
||||
@@ -268,11 +268,11 @@ class AuradineWebAPI(BaseWebAPI):
|
||||
"""
|
||||
return await self.send_command("mode")
|
||||
|
||||
async def set_mode(self, **kwargs) -> dict:
|
||||
async def set_mode(self, **kwargs: Any) -> dict:
|
||||
"""Set the operational mode of the Auradine miner.
|
||||
|
||||
Args:
|
||||
**kwargs: Mode settings specified as keyword arguments.
|
||||
**kwargs (Any): Mode settings specified as keyword arguments.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the mode setting operation.
|
||||
@@ -287,11 +287,11 @@ class AuradineWebAPI(BaseWebAPI):
|
||||
"""
|
||||
return await self.send_command("network")
|
||||
|
||||
async def set_network(self, **kwargs) -> dict:
|
||||
async def set_network(self, **kwargs: Any) -> dict:
|
||||
"""Set the network configuration of the Auradine miner.
|
||||
|
||||
Args:
|
||||
**kwargs: Network settings specified as keyword arguments.
|
||||
**kwargs (Any): Network settings specified as keyword arguments.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the network configuration.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user