Compare commits

...

47 Commits

Author SHA1 Message Date
Brett Rowan
b81276cb19 version: bump version number 2025-04-16 08:40:36 -06:00
Adrian
8dcc72b1bb T21 working on LuxOS (#331) 2025-04-14 12:55:54 -06:00
Timur Taipov
540572356f feature: add support for S21+ hydro (#326) 2025-04-14 09:15:06 -06:00
Ryan Heideman
83035a869b feature: Add Support for Avalon Nano 3s (#329) 2025-04-12 15:34:45 -06:00
Brett Rowan
4c104a59ff version: bump version number 2025-04-01 09:24:20 -06:00
Brett Rowan
e708ae3728 bug: remove MSKminer web identification 2025-04-01 09:23:51 -06:00
Wilfred Allyn
a4352816ee bug: pass atmset param as bool 2025-04-01 07:48:42 -06:00
Will Jackson
336bd9c002 bug: update set_dps configuration to handle optional power and hashrate parameters (#319)
* bug: update set_dps configuration to handle optional power and hashrate parameters
2025-03-27 11:09:47 -06:00
Wilfred Allyn
e3c917efde feature: set supports_autotuning True for luxminer 2025-03-26 08:06:38 -06:00
Brett Rowan
4d71012ed6 version: bump version number
Closes: #310
2025-03-20 10:46:30 -06:00
Will Jackson
1acdba8ae0 bug: fix set_dps configuration to handle power target parameters correctly (#318)
* bug: fix set_dps configuration to handle power target parameters correctly

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-03-20 10:45:48 -06:00
Brett Rowan
5567f26c03 version: bump version number
Closes: #316, #310
2025-03-20 09:20:51 -06:00
Brett Rowan
0027485582 bug: fix set_dps tuple
cc: #310
2025-03-20 09:20:23 -06:00
Brett Rowan
c9bcb7bab6 feature: add support for S21+
cc: #316
2025-03-19 13:28:03 -06:00
John-Paul Compagnone
0ee261930e epic: remove guard on expected_hashboards 2025-03-18 09:35:13 -06:00
John-Paul Compagnone
3cfc8dded9 epic: remove v1 from ELITE miner 2025-03-18 09:35:13 -06:00
John-Paul Compagnone
655cf6d0ac epic: add BlockMiner eLITE, S19k Pro Dualie 2025-03-18 09:35:13 -06:00
Brett Rowan
640dc6d8c2 version: bump version number 2025-03-18 09:00:40 -06:00
Brett Rowan
a572fedb4d bug: fix some cases where bosminer config could be invalid 2025-03-18 09:00:16 -06:00
Brett Rowan
6c46a7cd71 bug: handle cases where hashboards and fans can be None with unknown types 2025-03-18 08:18:34 -06:00
Brett Rowan
49f42172da version: bump version number 2025-03-17 14:19:47 -06:00
Brett Rowan
83be80e4bc bug: handle connection errors in grpc for boser 2025-03-17 14:19:28 -06:00
Brett Rowan
0be9e9d519 feature: add fault_light to influx 2025-03-17 14:17:37 -06:00
Upstream Data
4694c0f774 bug: fix error code handling in list 2025-03-06 11:45:38 -07:00
Brett Rowan
11012b310b bug: fix delimiter handling 2025-03-04 21:27:28 -07:00
Brett Rowan
8852fab3ee feature: allow the user to pass a delimiter to as_influx 2025-03-04 20:14:55 -07:00
Brett Rowan
ec1b2ca162 bug: fix bad " " handling in influx 2025-03-04 19:58:44 -07:00
Upstream Data
1f9d4c8c10 bug: swap to lower case booleans 2025-03-04 19:51:48 -07:00
Upstream Data
5bb04b2af4 refactor: round hashrate to 2 decimals for influx 2025-03-04 19:51:48 -07:00
Upstream Data
2e192a1536 feature: update support for influxdb line protocol 2025-03-04 19:51:48 -07:00
Brett Rowan
4da8044bc7 bug: fix bad type hint 2025-03-04 19:51:37 -07:00
Brett Rowan
76078e4d0e bug: fix wattage limit parsing on luxos 2025-03-04 19:51:13 -07:00
Upstream Data
276a476fab bug: fix json decode error possibility with avalon nanos 2025-03-03 15:04:22 -07:00
pre-commit-ci[bot]
e0abed4f93 [pre-commit.ci] pre-commit autoupdate (#307)
updates:
- [github.com/pycqa/isort: 6.0.0 → 6.0.1](https://github.com/pycqa/isort/compare/6.0.0...6.0.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-03-03 14:56:47 -07:00
Ganey
4a67bd5d99 feat: add powerlimit to avalon nano (#302)
* feat: add powerlimit to avalon nano

add power limit to avalon nano

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-02-26 15:23:18 -07:00
pre-commit-ci[bot]
eb48d04939 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/python-poetry/poetry: 2.0.1 → 2.1.1](https://github.com/python-poetry/poetry/compare/2.0.1...2.1.1)
2025-02-18 08:44:43 -07:00
Upstream Data
c4697728e4 version: bump version number 2025-02-13 15:38:53 -07:00
Upstream Data
de64172073 feature: add support for Hiveon S19kPro 2025-02-13 15:38:28 -07:00
Upstream Data
4d7a13433b version: bump version number 2025-02-12 16:08:23 -07:00
Adrian
e14a4791b2 it turns out there are 3 hashboards per miner (#293) 2025-02-12 16:07:46 -07:00
Upstream Data
0e76d4550b version: bump version number 2025-02-11 15:47:56 -07:00
Upstream Data
2d424025e9 feature: add MAC address to mskminer 2025-02-11 15:47:37 -07:00
Upstream Data
bf4903ce4b refactor: remove some unused imports 2025-02-11 09:38:31 -07:00
Upstream Data
4f7f6bf045 version: bump version number 2025-02-11 09:34:57 -07:00
Upstream Data
824890ec97 feature: add very basic support for MSKminer 2025-02-11 09:34:23 -07:00
Upstream Data
ce9d7ffb0f bug: fix issue with vnish preset parsing on some miners 2025-02-11 09:00:59 -07:00
Upstream Data
183b4934c1 refactor: remote unused import in test 2025-02-06 11:44:15 -07:00
68 changed files with 1680 additions and 271 deletions

View File

@@ -5,7 +5,7 @@ ci:
- generate-docs
repos:
- repo: https://github.com/python-poetry/poetry
rev: 2.0.1
rev: 2.1.1
hooks:
- id: poetry-check
- id: poetry-lock
@@ -27,7 +27,7 @@ repos:
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 6.0.0
rev: 6.0.1
hooks:
- id: isort
name: isort (python)

View File

@@ -62,6 +62,8 @@ def backend_str(backend: MinerTypes) -> str:
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")

View File

@@ -677,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
@@ -703,6 +716,19 @@
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
@@ -716,6 +742,19 @@
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

View File

@@ -40,6 +40,32 @@
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
## S21+ Hydro (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21PlusHydro
handler: python
options:
show_root_heading: false
heading_level: 0
## T21 (Stock)
- [x] Shutdowns
@@ -161,7 +187,7 @@
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X21.S21.LUXMinerS21
@@ -170,6 +196,19 @@
show_root_heading: false
heading_level: 0
## T21 (LuxOS)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X21.T21.LUXMinerT21
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 (MaraFW)
- [ ] Shutdowns

View File

@@ -14,3 +14,16 @@
show_root_heading: false
heading_level: 0
## Avalon Nano 3s (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.avalonminer.cgminer.nano.nano3.CGMinerAvalonNano3s
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -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

View File

@@ -103,6 +103,8 @@ 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_1-hydro-stock">S21+ Hydro (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>
@@ -553,6 +555,9 @@ details {
<ul>
<li><a href="../avalonminer/nano#avalon-nano-3-stock">Avalon Nano 3 (Stock)</a></li>
</ul>
<ul>
<li><a href="../avalonminer/nano#avalon-nano-3s-stock">Avalon Nano 3s (Stock)</a></li>
</ul>
</details>
<details>
<summary>A15X Series:</summary>
@@ -736,6 +741,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>
@@ -751,6 +757,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>
@@ -769,12 +776,24 @@ 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>
</details>
<details>
<summary>LuxOS Firmware Miners:</summary>
<ul>
<details>

187
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.0.1 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]
@@ -85,7 +85,26 @@ files = [
]
[package.extras]
dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"]
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"
@@ -435,7 +454,7 @@ files = [
[package.extras]
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)"]
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.6"
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.6-py3-none-any.whl", hash = "sha256:b2a3afe497c6c1f952e54a23095ecc09435016293e77af8478ed65df1022a394"},
{file = "griffe-1.5.6.tar.gz", hash = "sha256:181f6666d5aceb6cd6e2da5a2b646cfb431e47a0da1fda283845734b67e10944"},
{file = "griffe-1.6.0-py3-none-any.whl", hash = "sha256:9f1dfe035d4715a244ed2050dfbceb05b1f470809ed4f6bb10ece5a7302f8dd1"},
{file = "griffe-1.6.0.tar.gz", hash = "sha256:eb5758088b9c73ad61c7ac014f3cdfb4c57b5c2fcbfca69996584b702aefa354"},
]
[package.dependencies]
@@ -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.*)"]
@@ -589,14 +608,14 @@ files = [
[[package]]
name = "identify"
version = "2.6.6"
version = "2.6.8"
description = "File identification library for Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881"},
{file = "identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251"},
{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]
@@ -634,12 +653,12 @@ files = [
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.6.2"
version = "9.6.7"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
groups = ["docs"]
files = [
{file = "mkdocs_material-9.6.2-py3-none-any.whl", hash = "sha256:71d90dbd63b393ad11a4d90151dfe3dcbfcd802c0f29ce80bebd9bbac6abc753"},
{file = "mkdocs_material-9.6.2.tar.gz", hash = "sha256:a3de1c5d4c745f10afa78b1a02f917b9dce0808fb206adc0f5bb48b58c1ca21f"},
{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,7 +886,6 @@ 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]
@@ -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"
@@ -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.29.1"
version = "20.29.2"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779"},
{file = "virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35"},
{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,11 +1665,11 @@ 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]

View File

@@ -56,6 +56,16 @@ 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 {
@@ -209,6 +219,15 @@ 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."""
@@ -327,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)

View File

@@ -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()
@@ -95,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 {}

View File

@@ -55,6 +55,9 @@ 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"}
@@ -138,6 +141,9 @@ 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)}
@@ -191,6 +197,9 @@ 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"}
@@ -248,6 +257,20 @@ 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:

View File

@@ -52,6 +52,11 @@ 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}
@@ -77,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")
@@ -90,6 +98,11 @@ 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}
@@ -125,6 +138,11 @@ 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}
@@ -150,6 +168,11 @@ 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}
@@ -186,6 +209,11 @@ 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}
@@ -230,18 +258,18 @@ 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),
)
),
),
power_target_kwargs = {}
if self.scaling.step is not None:
power_target_kwargs["power_step"] = Power(self.scaling.step)
if self.scaling.minimum is not None:
power_target_kwargs["min_power_target"] = Power(self.scaling.minimum)
cfg["set_dps"] = SetDpsRequest(
save_action=SaveAction.SAVE_AND_APPLY,
enable=True,
**sd_cfg,
target=DpsTarget(power_target=DpsPowerTarget(**power_target_kwargs)),
)
return cfg
def as_auradine(self) -> dict:
@@ -288,6 +316,11 @@ 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}
@@ -297,7 +330,6 @@ class MiningModeHashrateTune(MinerConfigValue):
conf["hashrate_target"] = self.hashrate
return {"autotuning": conf}
@property
def as_boser(self) -> dict:
cfg = {
"set_performance_mode": SetPerformanceModeRequest(
@@ -317,18 +349,24 @@ 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),
)
),
hashrate_target_kwargs = {}
if self.scaling.step is not None:
hashrate_target_kwargs["hashrate_step"] = TeraHashrate(
self.scaling.step
)
if self.scaling.minimum is not None:
hashrate_target_kwargs["min_hashrate_target"] = TeraHashrate(
self.scaling.minimum
)
cfg["set_dps"] = SetDpsRequest(
save_action=SaveAction.SAVE_AND_APPLY,
enable=True,
**sd_cfg,
target=DpsTarget(
hashrate_target=DpsHashrateTarget(**hashrate_target_kwargs)
),
)
return cfg
def as_auradine(self) -> dict:
@@ -386,12 +424,10 @@ 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=MiningPreset.from_luxos(active_preset),
active_preset=active_preset,
available_presets=[
MiningPreset.from_luxos(p) for p in rpc_profiles["PROFILES"]
],
@@ -399,14 +435,14 @@ class MiningModePreset(MinerConfigValue):
@classmethod
def get_active_preset_from_luxos(
cls, rpc_config: dict, rpc_profiles: list[dict]
) -> dict:
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 active_preset
return MiningPreset.from_luxos(active_preset)
class ManualBoardSettings(MinerConfigValue):
@@ -422,6 +458,11 @@ 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}
@@ -549,6 +590,20 @@ 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:
@@ -645,6 +700,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]):

View File

@@ -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(

View File

@@ -43,6 +43,13 @@ 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,
@@ -153,6 +160,12 @@ 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(
@@ -177,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
@@ -248,6 +261,17 @@ 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
@@ -375,6 +399,13 @@ 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 = []
@@ -467,6 +498,11 @@ 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)}
@@ -569,10 +605,22 @@ 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"]

View File

@@ -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
@@ -28,6 +28,7 @@ from pyasic.device.algorithm.hashrate import AlgoHashRateType
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
@@ -107,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
@@ -135,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:
@@ -307,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)
@@ -365,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:
@@ -374,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])

View File

@@ -28,6 +28,8 @@ class HashBoard(BaseModel):
Attributes:
slot: The slot of the board as an int.
hashrate: The hashrate of the board in TH/s as a float.
inlet_temp: Inlet temperature for hydro asics as an int
outlet_temp: Outlet temperature for hydro asics as an int
temp: The temperature of the PCB as an int.
chip_temp: The temperature of the chips as an int.
chips: The chip count of the board as an int.
@@ -41,6 +43,8 @@ class HashBoard(BaseModel):
slot: int = 0
hashrate: AlgoHashRateType | None = None
inlet_temp: float | None = None
outlet_temp: float | None = None
temp: float | None = None
chip_temp: float | None = None
chips: int | None = None
@@ -51,6 +55,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 +75,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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -58,6 +58,8 @@ class AntminerModels(MinerModelType):
S19jXP = "S19j XP"
T19 = "T19"
S21 = "S21"
S21Plus = "S21+"
S21PlusHydro = "S21+ Hydro"
S21Pro = "S21 Pro"
S21Hydro = "S21 Hydro"
T21 = "T21"
@@ -450,6 +452,7 @@ class AvalonminerModels(MinerModelType):
Avalon1246 = "Avalon 1246"
Avalon1566 = "Avalon 1566"
AvalonNano3 = "Avalon Nano 3"
AvalonNano3s = "Avalon Nano 3s"
def __str__(self):
return self.value
@@ -480,7 +483,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

View File

@@ -21,4 +21,5 @@ from .epic import *
from .hiveon import *
from .luxos import *
from .marathon import *
from .mskminer import *
from .vnish import *

View File

@@ -15,13 +15,21 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerModern
from pyasic.miners.device.models import S21, S21Hydro, S21Pro
from pyasic.miners.device.models import S21, S21Hydro, S21Plus, S21PlusHydro, S21Pro
class BMMinerS21(AntminerModern, S21):
pass
class BMMinerS21Plus(AntminerModern, S21Plus):
pass
class BMMinerS21PlusHydro(AntminerModern, S21PlusHydro):
pass
class BMMinerS21Pro(AntminerModern, S21Pro):
pass

View File

@@ -13,5 +13,11 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from .S21 import BMMinerS21, BMMinerS21Hydro, BMMinerS21Pro
from .S21 import (
BMMinerS21,
BMMinerS21Hydro,
BMMinerS21Plus,
BMMinerS21PlusHydro,
BMMinerS21Pro,
)
from .T21 import BMMinerT21

View File

@@ -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

View File

@@ -21,6 +21,7 @@ from .S19 import (
ePICS19jProDual,
ePICS19jProPlus,
ePICS19kPro,
ePICS19kProDual,
ePICS19Pro,
ePICS19XP,
)

View File

@@ -100,5 +100,5 @@ class HiveonS19ProPlusHydro(HiveonModern, S19ProPlusHydro):
pass
class HiveonS19KPro(HiveonModern, S19KPro):
class HiveonS19kPro(HiveonModern, S19KPro):
pass

View File

@@ -23,7 +23,7 @@ from .S19 import (
HiveonS19j,
HiveonS19jNoPIC,
HiveonS19jPro,
HiveonS19KPro,
HiveonS19kPro,
HiveonS19L,
HiveonS19NoPIC,
HiveonS19Plus,

View File

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

View File

@@ -15,3 +15,4 @@
# ------------------------------------------------------------------------------
from .S21 import LUXMinerS21
from .T21 import LUXMinerT21

View 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

View 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

View 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 *

View File

@@ -14,4 +14,4 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .nano3 import CGMinerAvalonNano3
from .nano3 import CGMinerAvalonNano3, CGMinerAvalonNano3s

View File

@@ -24,7 +24,7 @@ from pyasic.miners.data import (
RPCAPICommand,
WebAPICommand,
)
from pyasic.miners.device.models import AvalonNano3
from pyasic.miners.device.models import AvalonNano3, AvalonNano3s
from pyasic.web.avalonminer import AvalonMinerWebAPI
AVALON_NANO_DATA_LOC = DataLocations(
@@ -105,3 +105,7 @@ class CGMinerAvalonNano3(AvalonMiner, AvalonNano3):
return mac.upper()
except (KeyError, ValueError):
pass
class CGMinerAvalonNano3s(AvalonMiner, AvalonNano3s):
pass

View File

@@ -32,6 +32,7 @@ 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

View File

@@ -252,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)
@@ -269,18 +272,47 @@ class AntminerModern(BMMiner):
rate=board["rate_real"], unit=self.algo.unit.GH
).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 == 0, board["temp_chip"])
)
hashboards[board["index"]].chip_temp = sum(chip_temp_data) / len(
chip_temp_data
)
if "S21+ Hyd" in self.model:
hashboards[board["index"]].inlet_temp = board["temp_pcb"][0]
hashboards[board["index"]].outlet_temp = board["temp_pcb"][2]
hashboards[board["index"]].chip_temp = board["temp_pic"][0]
board_temp_data = list(
filter(
lambda x: not x == 0,
[
board["temp_pic"][1],
board["temp_pic"][2],
board["temp_pic"][3],
board["temp_pcb"][1],
board["temp_pcb"][3],
],
)
)
hashboards[board["index"]].temp = (
sum(board_temp_data) / len(board_temp_data)
if len(board_temp_data) > 0
else 0
)
else:
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)
if len(board_temp_data) > 0
else 0
)
chip_temp_data = list(
filter(lambda x: not x == 0, board["temp_chip"])
)
hashboards[board["index"]].chip_temp = (
sum(chip_temp_data) / len(chip_temp_data)
if len(chip_temp_data) > 0
else 0
)
hashboards[board["index"]].serial_number = board["sn"]
hashboards[board["index"]].missing = False
except LookupError:
@@ -565,6 +597,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()
@@ -593,6 +628,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:

View File

@@ -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()

View File

@@ -104,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()
@@ -195,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)
@@ -309,6 +329,9 @@ class AvalonMiner(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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -209,6 +209,9 @@ class ElphapexMiner(StockFirmware):
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)
@@ -317,6 +320,9 @@ class ElphapexMiner(StockFirmware):
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()

View File

@@ -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()

View File

@@ -141,6 +141,9 @@ class ESPMiner(BaseMiner):
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()
@@ -169,6 +172,9 @@ class ESPMiner(BaseMiner):
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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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"]

View File

@@ -86,6 +86,7 @@ class LUXMiner(LuxOSFirmware):
supports_shutdown = True
supports_presets = True
supports_autotuning = True
data_locations = LUXMINER_DATA_LOC
@@ -152,7 +153,7 @@ 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:
@@ -191,10 +192,10 @@ class LUXMiner(LuxOSFirmware):
try:
if await self.atm_enabled():
re_enable_atm = True
await self.rpc.atmset("enabled=false")
await self.rpc.atmset(enabled=False)
result = await self.rpc.profileset(new_preset)
if re_enable_atm:
await self.rpc.atmset("enabled=true")
await self.rpc.atmset(enabled=True)
except APIError:
raise
except Exception as e:
@@ -240,6 +241,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)
@@ -309,6 +313,9 @@ class LUXMiner(LuxOSFirmware):
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()

View File

@@ -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()

View 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

View File

@@ -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

View File

@@ -14,4 +14,4 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .blockminer import ePICBlockMiner520i, ePICBlockMiner720i
from .blockminer import *

View File

@@ -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

View File

@@ -27,6 +27,24 @@ 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 S21PlusHydro(AntMinerMake):
raw_model = MinerModel.ANTMINER.S21PlusHydro
expected_chips = 95
expected_fans = 0
expected_hashboards = 3
algo = MinerAlgo.SHA256
class S21Pro(AntMinerMake):
raw_model = MinerModel.ANTMINER.S21Pro
@@ -40,6 +58,6 @@ class S21Hydro(AntMinerMake):
raw_model = MinerModel.ANTMINER.S21Hydro
expected_chips = 216
expected_hashboards = 2
expected_hashboards = 3
expected_fans = 0
algo = MinerAlgo.SHA256

View File

@@ -14,5 +14,5 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .S21 import S21, S21Hydro, S21Pro
from .S21 import S21, S21Hydro, S21Plus, S21PlusHydro, S21Pro
from .T21 import T21

View File

@@ -1 +1 @@
from .nano3 import AvalonNano3
from .nano3 import AvalonNano3, AvalonNano3s

View File

@@ -10,3 +10,12 @@ class AvalonNano3(AvalonMinerMake):
expected_chips = 10
expected_fans = 1
algo = MinerAlgo.SHA256
class AvalonNano3s(AvalonMinerMake):
raw_model = MinerModel.AVALONMINER.AvalonNano3s
expected_hashboards = 1
expected_chips = 12
expected_fans = 1
algo = MinerAlgo.SHA256

View File

@@ -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

View File

@@ -66,6 +66,7 @@ class MinerTypes(enum.Enum):
VOLCMINER = 15
LUCKYMINER = 16
ELPHAPEX = 17
MSKMINER = 18
MINER_CLASSES = {
@@ -119,6 +120,8 @@ MINER_CLASSES = {
"ANTMINER S21": BMMinerS21,
"ANTMINER BHB68601": BMMinerS21, # ???
"ANTMINER BHB68606": BMMinerS21, # ???
"ANTMINER S21+": BMMinerS21Plus,
"ANTMINER S21+ HYD.": BMMinerS21PlusHydro,
"ANTMINER S21 PRO": BMMinerS21Pro,
"ANTMINER T21": BMMinerT21,
"ANTMINER S21 HYD.": BMMinerS21Hydro,
@@ -504,6 +507,7 @@ MINER_CLASSES = {
"AVALONMINER 1166PRO": CGMinerAvalon1166Pro,
"AVALONMINER 1246": CGMinerAvalon1246,
"AVALONMINER NANO3": CGMinerAvalonNano3,
"AVALON NANO3S": CGMinerAvalonNano3s,
"AVALONMINER 15-194": CGMinerAvalon1566,
},
MinerTypes.INNOSILICON: {
@@ -591,16 +595,23 @@ 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,
"ANTMINER S9": LUXMinerS9,
@@ -612,6 +623,7 @@ MINER_CLASSES = {
"ANTMINER S19 XP": LUXMinerS19XP,
"ANTMINER T19": LUXMinerT19,
"ANTMINER S21": LUXMinerS21,
"ANTMINER T21": LUXMinerT21,
},
MinerTypes.AURADINE: {
None: type("AuradineUnknown", (Auradine, AuradineMake), {}),
@@ -946,6 +958,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 (
@@ -1411,6 +1425,13 @@ class MinerFactory:
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()

View File

@@ -41,6 +41,7 @@ _settings = { # defaults
"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",
}

View File

@@ -64,7 +64,7 @@ class AvalonMinerWebAPI(BaseWebAPI):
resp = await client.get(url)
raw_data = resp.text.replace("minerinfoCallback(", "").replace(");", "")
return json.loads(raw_data)
except httpx.HTTPError:
except (httpx.HTTPError, json.JSONDecodeError):
pass
return {}

View File

@@ -89,7 +89,7 @@ class BOSerWebAPI(BaseWebAPI):
for cmd in tasks:
try:
result[cmd] = await tasks[cmd]
except (GRPCError, APIError):
except (GRPCError, APIError, ConnectionError):
pass
return result
@@ -121,7 +121,7 @@ class BOSerWebAPI(BaseWebAPI):
metadata = [("authorization", await self.auth())]
return (await endpoint(message, metadata=metadata)).to_pydict()
raise e
except GRPCError as e:
except (GRPCError, ConnectionError) as e:
raise APIError(f"gRPC command failed - {endpoint}") from e
async def auth(self) -> str | None:

72
pyasic/web/mskminer.py Normal file
View File

@@ -0,0 +1,72 @@
# ------------------------------------------------------------------------------
# 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 __future__ import annotations
import asyncio
import warnings
from typing import Any
import httpx
from pyasic import settings
from pyasic.errors import APIError
from pyasic.web.base import BaseWebAPI
class MSKMinerWebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.username = "admin"
self.pwd = settings.get("default_mskminer_web_password", "root")
async def multicommand(
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
) -> dict:
tasks = {c: asyncio.create_task(getattr(self, c)()) for c in commands}
await asyncio.gather(*[t for t in tasks.values()])
return {t: tasks[t].result() for t in tasks}
async def send_command(
self,
command: str | bytes,
ignore_errors: bool = False,
allow_warning: bool = True,
privileged: bool = False,
**parameters: Any,
) -> dict:
async with httpx.AsyncClient(transport=settings.transport()) as client:
try:
# auth
await client.post(
f"http://{self.ip}:{self.port}/admin/login",
data={"username": self.username, "password": self.pwd},
)
except httpx.HTTPError:
warnings.warn(f"Could not authenticate with miner web: {self}")
try:
resp = await client.post(
f"http://{self.ip}:{self.port}/api/{command}", params=parameters
)
if not resp.status_code == 200:
if not ignore_errors:
raise APIError(f"Command failed: {command}")
warnings.warn(f"Command failed: {command}")
return resp.json()
except httpx.HTTPError:
raise APIError(f"Command failed: {command}")
async def info_v1(self):
return await self.send_command("info_v1")

View File

@@ -1,6 +1,6 @@
[project]
name = "pyasic"
version = "0.71.8"
version = "0.72.6"
description = "A simplified and standardized interface for Bitcoin ASICs."
authors = [{name = "UpstreamData", email = "brett@upstreamdata.ca"}]

View File

@@ -7,7 +7,6 @@ from unittest.mock import patch
from pyasic import APIError, MinerData
from pyasic.data import Fan, HashBoard
from pyasic.device.algorithm import SHA256Unit
from pyasic.device.algorithm.hashrate.unit.scrypt import ScryptUnit
from pyasic.miners.avalonminer import CGMinerAvalon1566
POOLS = [

View File

@@ -0,0 +1 @@
from .version_2_6_0_39 import TestMSKMiners

View File

@@ -0,0 +1,500 @@
"""Tests for MSK miner firmware version 2.6.0.39"""
import unittest
from dataclasses import fields
from unittest.mock import patch
from pyasic import APIError, MinerData
from pyasic.data import Fan, HashBoard
from pyasic.device.algorithm import SHA256Unit
from pyasic.miners.antminer import MSKMinerS19NoPIC
POOLS = [
{
"url": "stratum+tcp://stratum.pool.io:3333",
"user": "pool_username.real_worker",
"pwd": "123",
},
{
"url": "stratum+tcp://stratum.pool.io:3334",
"user": "pool_username.real_worker",
"pwd": "123",
},
{
"url": "stratum+tcp://stratum.pool.io:3335",
"user": "pool_username.real_worker",
"pwd": "123",
},
]
data = {
MSKMinerS19NoPIC: {
"web_info_v1": {
# needs updates with real data
"network_info": {
"result": {
"address": "192.168.1.10",
"macaddr": "12:34:56:78:90:12",
"netmask": "255.255.255.0",
}
}
},
"rpc_version": {
"STATUS": [
{
"STATUS": "S",
"When": 1738856891,
"Code": 22,
"Msg": "BMMiner versions",
"Description": "bmminer 1.0.0",
}
],
"VERSION": [
{
"BMMiner": "4.11.1 rwglr",
"API": "3.1",
"Miner": "0.0.1.3",
"CompileTime": "10 Dec 2024 14:34:31 GMT",
"Type": "S19-88 v.2.6.0.39 ",
}
],
"id": 1,
},
"rpc_stats": {
"STATUS": [
{
"STATUS": "S",
"When": 1738856891,
"Code": 70,
"Msg": "BMMiner stats",
"Description": "bmminer 1.0.0",
}
],
"STATS": [
{
"BMMiner": "4.11.1 rwglr",
"Miner": "0.0.1.3",
"CompileTime": "10 Dec 2024 14:34:31 GMT",
"Type": "S19-88 v.2.6.0.39 ",
},
{
"STATS": 0,
"ID": "BC50",
"Elapsed": 1926,
"Calls": 0,
"Wait": 0.000000,
"Max": 0.000000,
"Min": 99999999.000000,
"GHS 5s": 99989.59,
"GHS av": 99761.40,
"miner_count": 3,
"frequency": "",
"fan_num": 4,
"fan1": 5010,
"fan2": 5160,
"fan3": 5070,
"fan4": 5040,
"fan5": 0,
"fan6": 0,
"fan7": 0,
"fan8": 0,
"temp_num": 3,
"temp1": 45,
"temp2": 45,
"temp3": 47,
"temp4": 0,
"temp5": 0,
"temp6": 0,
"temp7": 0,
"temp8": 0,
"temp9": 0,
"temp10": 0,
"temp11": 0,
"temp12": 0,
"temp13": 0,
"temp14": 0,
"temp15": 0,
"temp16": 0,
"temp2_1": 59,
"temp2_2": 57,
"temp2_3": 58,
"temp2_4": 0,
"temp2_5": 0,
"temp2_6": 0,
"temp2_7": 0,
"temp2_8": 0,
"temp2_9": 0,
"temp2_10": 0,
"temp2_11": 0,
"temp2_12": 0,
"temp2_13": 0,
"temp2_14": 0,
"temp2_15": 0,
"temp2_16": 0,
"temp3_1": 59,
"temp3_2": 56,
"temp3_3": 57,
"temp3_4": 0,
"temp3_5": 0,
"temp3_6": 0,
"temp3_7": 0,
"temp3_8": 0,
"temp3_9": 0,
"temp3_10": 0,
"temp3_11": 0,
"temp3_12": 0,
"temp3_13": 0,
"temp3_14": 0,
"temp3_15": 0,
"temp3_16": 0,
"temp_pcb1": "45-42-45-42",
"temp_pcb2": "45-42-45-42",
"temp_pcb3": "47-43-47-43",
"temp_pcb4": "0-0-0-0",
"temp_pcb5": "0-0-0-0",
"temp_pcb6": "0-0-0-0",
"temp_pcb7": "0-0-0-0",
"temp_pcb8": "0-0-0-0",
"temp_pcb9": "0-0-0-0",
"temp_pcb10": "0-0-0-0",
"temp_pcb11": "0-0-0-0",
"temp_pcb12": "0-0-0-0",
"temp_pcb13": "0-0-0-0",
"temp_pcb14": "0-0-0-0",
"temp_pcb15": "0-0-0-0",
"temp_pcb16": "0-0-0-0",
"temp_chip1": "59-59-59-59",
"temp_chip2": "57-56-57-56",
"temp_chip3": "58-57-58-57",
"temp_chip4": "0-0-0-0",
"temp_chip5": "0-0-0-0",
"temp_chip6": "0-0-0-0",
"temp_chip7": "0-0-0-0",
"temp_chip8": "0-0-0-0",
"temp_chip9": "0-0-0-0",
"temp_chip10": "0-0-0-0",
"temp_chip11": "0-0-0-0",
"temp_chip12": "0-0-0-0",
"temp_chip13": "0-0-0-0",
"temp_chip14": "0-0-0-0",
"temp_chip15": "0-0-0-0",
"temp_chip16": "0-0-0-0",
"total_rateideal": 99674.88,
"total_freqavg": 0.00,
"total_acn": 264,
"total_rate": 99989.59,
"chain_rateideal1": 33677.28,
"chain_rateideal2": 32788.06,
"chain_rateideal3": 33209.54,
"chain_rateideal4": 31436.24,
"chain_rateideal5": 31436.24,
"chain_rateideal6": 31436.24,
"chain_rateideal7": 31436.24,
"chain_rateideal8": 31436.24,
"chain_rateideal9": 31436.24,
"chain_rateideal10": 31436.24,
"chain_rateideal11": 31436.24,
"chain_rateideal12": 31436.24,
"chain_rateideal13": 31436.24,
"chain_rateideal14": 31436.24,
"chain_rateideal15": 31436.24,
"chain_rateideal16": 31436.24,
"temp_max": 47,
"no_matching_work": 0,
"chain_acn1": 88,
"chain_acn2": 88,
"chain_acn3": 88,
"chain_acn4": 0,
"chain_acn5": 0,
"chain_acn6": 0,
"chain_acn7": 0,
"chain_acn8": 0,
"chain_acn9": 0,
"chain_acn10": 0,
"chain_acn11": 0,
"chain_acn12": 0,
"chain_acn13": 0,
"chain_acn14": 0,
"chain_acn15": 0,
"chain_acn16": 0,
"chain_acs1": " oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo",
"chain_acs2": " oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo",
"chain_acs3": " oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo",
"chain_acs4": "",
"chain_acs5": "",
"chain_acs6": "",
"chain_acs7": "",
"chain_acs8": "",
"chain_acs9": "",
"chain_acs10": "",
"chain_acs11": "",
"chain_acs12": "",
"chain_acs13": "",
"chain_acs14": "",
"chain_acs15": "",
"chain_acs16": "",
"chain_hw1": 0,
"chain_hw2": 0,
"chain_hw3": 0,
"chain_hw4": 0,
"chain_hw5": 0,
"chain_hw6": 0,
"chain_hw7": 0,
"chain_hw8": 0,
"chain_hw9": 0,
"chain_hw10": 0,
"chain_hw11": 0,
"chain_hw12": 0,
"chain_hw13": 0,
"chain_hw14": 0,
"chain_hw15": 0,
"chain_hw16": 0,
"chain_rate1": 34084.86,
"chain_rate2": 32303.65,
"chain_rate3": 33601.08,
"chain_rate4": 0.00,
"chain_rate5": 0.00,
"chain_rate6": 0.00,
"chain_rate7": 0.00,
"chain_rate8": 0.00,
"chain_rate9": 0.00,
"chain_rate10": 0.00,
"chain_rate11": 0.00,
"chain_rate12": 0.00,
"chain_rate13": 0.00,
"chain_rate14": 0.00,
"chain_rate15": 0.00,
"chain_rate16": 0.00,
"chain_xtime1": "{}",
"chain_xtime2": "{}",
"chain_xtime3": "{}",
"chain_offside_1": "",
"chain_offside_2": "",
"chain_offside_3": "",
"chain_opencore_0": "1",
"chain_opencore_1": "1",
"chain_opencore_2": "1",
"freq1": 744,
"freq2": 724,
"freq3": 734,
"freq4": 0,
"freq5": 0,
"freq6": 0,
"freq7": 0,
"freq8": 0,
"freq9": 0,
"freq10": 0,
"freq11": 0,
"freq12": 0,
"freq13": 0,
"freq14": 0,
"freq15": 0,
"freq16": 0,
"chain_avgrate1": 33585.34,
"chain_avgrate2": 32788.97,
"chain_avgrate3": 33336.44,
"chain_avgrate4": 0.00,
"chain_avgrate5": 0.00,
"chain_avgrate6": 0.00,
"chain_avgrate7": 0.00,
"chain_avgrate8": 0.00,
"chain_avgrate9": 0.00,
"chain_avgrate10": 0.00,
"chain_avgrate11": 0.00,
"chain_avgrate12": 0.00,
"chain_avgrate13": 0.00,
"chain_avgrate14": 0.00,
"chain_avgrate15": 0.00,
"chain_avgrate16": 0.00,
"miner_version": "0.0.1.3",
"miner_id": "",
"chain_power1": 1135,
"chain_power2": 1103,
"chain_power3": 1118,
"total_power": 3358,
"chain_voltage1": 15.70,
"chain_voltage2": 15.70,
"chain_voltage3": 15.70,
"chain_voltage4": 15.70,
"chain_voltage5": 15.70,
"chain_voltage6": 15.70,
"chain_voltage7": 15.70,
"chain_voltage8": 15.70,
"chain_voltage9": 15.70,
"chain_voltage10": 15.70,
"chain_voltage11": 15.70,
"chain_voltage12": 15.70,
"chain_voltage13": 15.70,
"chain_voltage14": 15.70,
"chain_voltage15": 15.70,
"chain_voltage16": 15.70,
"fan_pwm": 82,
"bringup_temp": 16,
"has_pic": "1",
"tune_running": "0",
"psu_status": "PSU OK",
"downscale_mode": "0",
"has_hotel_fee": "0",
},
],
"id": 1,
},
"rpc_pools": {
"STATUS": [
{
"Code": 7,
"Description": "cgminer 1.0.0",
"Msg": "3 Pool(s)",
"STATUS": "S",
"When": 1732121693,
}
],
"POOLS": [
{
"Accepted": 10000,
"Best Share": 1000000000.0,
"Diff": "100K",
"Diff1 Shares": 0,
"Difficulty Accepted": 1000000000.0,
"Difficulty Rejected": 1000000.0,
"Difficulty Stale": 0.0,
"Discarded": 100000,
"Get Failures": 3,
"Getworks": 9000,
"Has GBT": False,
"Has Stratum": True,
"Last Share Difficulty": 100000.0,
"Last Share Time": "0:00:02",
"Long Poll": "N",
"POOL": 0,
"Pool Rejected%": 0.0,
"Pool Stale%%": 0.0,
"Priority": 0,
"Proxy": "",
"Proxy Type": "",
"Quota": 1,
"Rejected": 100,
"Remote Failures": 0,
"Stale": 0,
"Status": "Alive",
"Stratum Active": True,
"Stratum URL": "stratum.pool.io",
"URL": "stratum+tcp://stratum.pool.io:3333",
"User": "pool_username.real_worker",
},
{
"Accepted": 10000,
"Best Share": 1000000000.0,
"Diff": "100K",
"Diff1 Shares": 0,
"Difficulty Accepted": 1000000000.0,
"Difficulty Rejected": 1000000.0,
"Difficulty Stale": 0.0,
"Discarded": 100000,
"Get Failures": 3,
"Getworks": 9000,
"Has GBT": False,
"Has Stratum": True,
"Last Share Difficulty": 100000.0,
"Last Share Time": "0:00:02",
"Long Poll": "N",
"POOL": 1,
"Pool Rejected%": 0.0,
"Pool Stale%%": 0.0,
"Priority": 0,
"Proxy": "",
"Proxy Type": "",
"Quota": 1,
"Rejected": 100,
"Remote Failures": 0,
"Stale": 0,
"Status": "Alive",
"Stratum Active": True,
"Stratum URL": "stratum.pool.io",
"URL": "stratum+tcp://stratum.pool.io:3333",
"User": "pool_username.real_worker",
},
{
"Accepted": 10000,
"Best Share": 1000000000.0,
"Diff": "100K",
"Diff1 Shares": 0,
"Difficulty Accepted": 1000000000.0,
"Difficulty Rejected": 1000000.0,
"Difficulty Stale": 0.0,
"Discarded": 100000,
"Get Failures": 3,
"Getworks": 9000,
"Has GBT": False,
"Has Stratum": True,
"Last Share Difficulty": 100000.0,
"Last Share Time": "0:00:02",
"Long Poll": "N",
"POOL": 2,
"Pool Rejected%": 0.0,
"Pool Stale%%": 0.0,
"Priority": 0,
"Proxy": "",
"Proxy Type": "",
"Quota": 1,
"Rejected": 100,
"Remote Failures": 0,
"Stale": 0,
"Status": "Alive",
"Stratum Active": True,
"Stratum URL": "stratum.pool.io",
"URL": "stratum+tcp://stratum.pool.io:3333",
"User": "pool_username.real_worker",
},
],
"id": 1,
},
}
}
class TestMSKMiners(unittest.IsolatedAsyncioTestCase):
@patch("pyasic.rpc.base.BaseMinerRPCAPI._send_bytes")
async def test_all_data_gathering(self, mock_send_bytes):
mock_send_bytes.raises = APIError()
for m_type in data:
gathered_data = {}
miner = m_type("127.0.0.1")
for data_name in fields(miner.data_locations):
if data_name.name == "config":
# skip
continue
data_func = getattr(miner.data_locations, data_name.name)
fn_args = data_func.kwargs
args_to_send = {k.name: data[m_type][k.name] for k in fn_args}
function = getattr(miner, data_func.cmd)
gathered_data[data_name.name] = await function(**args_to_send)
result = MinerData(
ip=str(miner.ip),
device_info=miner.device_info,
expected_chips=(
miner.expected_chips * miner.expected_hashboards
if miner.expected_chips is not None
else 0
),
expected_hashboards=miner.expected_hashboards,
expected_fans=miner.expected_fans,
hashboards=[
HashBoard(slot=i, expected_chips=miner.expected_chips)
for i in range(miner.expected_hashboards)
],
)
for item in gathered_data:
if gathered_data[item] is not None:
setattr(result, item, gathered_data[item])
self.assertEqual(result.mac, "12:34:56:78:90:12")
self.assertEqual(result.api_ver, "3.1")
self.assertEqual(result.fw_ver, "10 Dec 2024 14:34:31 GMT")
self.assertEqual(round(result.hashrate.into(SHA256Unit.TH)), 100)
self.assertEqual(
result.fans,
[Fan(speed=5010), Fan(speed=5160), Fan(speed=5070), Fan(speed=5040)],
)
self.assertEqual(result.total_chips, result.expected_chips)