feature: swap from @dataclass to pydantic BaseModel (#238)

* feature: switch almost everything to pydantic BaseModel

* feature: swap more dataclasses over to pydantic models

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

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

* bug: fix some initialization problems with miner mode config

* bug: fix new BOS+ pool parsing

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

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

* bug: add explicit type conversions to hashrate types

* bug: fix epic pool URL parsing

* bug: fix positional args in hashboards

* bug: fix epic missing url scheme

* convert board temp to int

---------

Co-authored-by: Upstream Data <brett@upstreamdata.ca>
Co-authored-by: John-Paul Compagnone <jpcompagnone@epicblockchain.io>
This commit is contained in:
Brett Rowan
2024-11-20 13:42:41 -07:00
committed by GitHub
parent 65ed565220
commit d66739e2c9
46 changed files with 597 additions and 485 deletions

173
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. # This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
[[package]] [[package]]
name = "aiofiles" name = "aiofiles"
@@ -11,6 +11,17 @@ files = [
{file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"},
] ]
[[package]]
name = "annotated-types"
version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
[[package]] [[package]]
name = "anyio" name = "anyio"
version = "4.6.2.post1" version = "4.6.2.post1"
@@ -380,13 +391,13 @@ files = [
[[package]] [[package]]
name = "httpcore" name = "httpcore"
version = "1.0.6" version = "1.0.7"
description = "A minimal low-level HTTP client." description = "A minimal low-level HTTP client."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"}, {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
{file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
] ]
[package.dependencies] [package.dependencies]
@@ -437,13 +448,13 @@ files = [
[[package]] [[package]]
name = "identify" name = "identify"
version = "2.6.1" version = "2.6.2"
description = "File identification library for Python" description = "File identification library for Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.9"
files = [ files = [
{file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, {file = "identify-2.6.2-py2.py3-none-any.whl", hash = "sha256:c097384259f49e372f4ea00a19719d95ae27dd5ff0fd77ad630aa891306b82f3"},
{file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, {file = "identify-2.6.2.tar.gz", hash = "sha256:fab5c716c24d7a789775228823797296a2994b075fb6080ac83a102772a98cbd"},
] ]
[package.extras] [package.extras]
@@ -837,13 +848,13 @@ files = [
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "24.1" version = "24.2"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
] ]
[[package]] [[package]]
@@ -936,6 +947,130 @@ files = [
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
] ]
[[package]]
name = "pydantic"
version = "2.9.2"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"},
{file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
pydantic-core = "2.23.4"
typing-extensions = [
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
{version = ">=4.6.1", markers = "python_version < \"3.13\""},
]
[package.extras]
email = ["email-validator (>=2.0.0)"]
timezone = ["tzdata"]
[[package]]
name = "pydantic-core"
version = "2.23.4"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"},
{file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"},
{file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"},
{file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"},
{file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"},
{file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"},
{file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"},
{file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"},
{file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"},
{file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"},
{file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"},
{file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"},
{file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"},
{file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"},
{file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"},
{file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"},
{file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"},
{file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"},
{file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"},
{file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"},
{file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"},
{file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"},
{file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"},
{file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"},
{file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"},
{file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"},
{file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"},
{file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"},
{file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"},
{file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"},
{file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"},
{file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"},
{file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"},
{file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"},
{file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"},
{file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"},
{file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"},
]
[package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]] [[package]]
name = "pymdown-extensions" name = "pymdown-extensions"
version = "10.12" version = "10.12"
@@ -1068,13 +1203,13 @@ files = [
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.0.2" version = "2.1.0"
description = "A lil' TOML parser" description = "A lil' TOML parser"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"},
{file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"},
] ]
[[package]] [[package]]
@@ -1163,13 +1298,13 @@ watchmedo = ["PyYAML (>=3.10)"]
[[package]] [[package]]
name = "zipp" name = "zipp"
version = "3.20.2" version = "3.21.0"
description = "Backport of pathlib-compatible object wrapper for zip files" description = "Backport of pathlib-compatible object wrapper for zip files"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.9"
files = [ files = [
{file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"},
{file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"},
] ]
[package.extras] [package.extras]
@@ -1183,4 +1318,4 @@ type = ["pytest-mypy"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "d611b5e8b0c5611d6ee916cedfb7f07f20dfc90a675ebaed04188e8b3c96aabe" content-hash = "0ae7d5fe3858e93c8a8715b11876be854ce8bf39685316ca1bee759bf0896ce6"

View File

@@ -13,25 +13,25 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from dataclasses import asdict, dataclass, field
from pyasic.config.fans import FanModeConfig from pydantic import BaseModel, Field
from pyasic.config.mining import MiningModeConfig
from pyasic.config.fans import FanMode, FanModeConfig
from pyasic.config.mining import MiningMode, MiningModeConfig
from pyasic.config.mining.scaling import ScalingConfig from pyasic.config.mining.scaling import ScalingConfig
from pyasic.config.pools import PoolConfig from pyasic.config.pools import PoolConfig
from pyasic.config.temperature import TemperatureConfig from pyasic.config.temperature import TemperatureConfig
from pyasic.misc import merge_dicts from pyasic.misc import merge_dicts
@dataclass class MinerConfig(BaseModel):
class MinerConfig:
"""Represents the configuration for a miner including pool configuration, """Represents the configuration for a miner including pool configuration,
fan mode, temperature settings, mining mode, and power scaling.""" fan mode, temperature settings, mining mode, and power scaling."""
pools: PoolConfig = field(default_factory=PoolConfig.default) pools: PoolConfig = Field(default_factory=PoolConfig.default)
fan_mode: FanModeConfig = field(default_factory=FanModeConfig.default) fan_mode: FanMode = Field(default_factory=FanModeConfig.default)
temperature: TemperatureConfig = field(default_factory=TemperatureConfig.default) temperature: TemperatureConfig = Field(default_factory=TemperatureConfig.default)
mining_mode: MiningModeConfig = field(default_factory=MiningModeConfig.default) mining_mode: MiningMode = Field(default_factory=MiningModeConfig.default)
def __getitem__(self, item): def __getitem__(self, item):
try: try:
@@ -41,7 +41,7 @@ class MinerConfig:
def as_dict(self) -> dict: def as_dict(self) -> dict:
"""Converts the MinerConfig object to a dictionary.""" """Converts the MinerConfig object to a dictionary."""
return asdict(self) return self.model_dump()
def as_am_modern(self, user_suffix: str = None) -> dict: def as_am_modern(self, user_suffix: str = None) -> dict:
"""Generates the configuration in the format suitable for modern Antminers.""" """Generates the configuration in the format suitable for modern Antminers."""
@@ -107,7 +107,7 @@ class MinerConfig:
} }
def as_boser(self, user_suffix: str = None) -> dict: def as_boser(self, user_suffix: str = None) -> dict:
""" "Generates the configuration in the format suitable for BOSer.""" """Generates the configuration in the format suitable for BOSer."""
return { return {
**self.fan_mode.as_boser(), **self.fan_mode.as_boser(),
**self.temperature.as_boser(), **self.temperature.as_boser(),

View File

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

View File

@@ -15,14 +15,15 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, field from typing import TypeVar, Union
from pydantic import Field
from pyasic.config.base import MinerConfigOption, MinerConfigValue from pyasic.config.base import MinerConfigOption, MinerConfigValue
@dataclass
class FanModeNormal(MinerConfigValue): class FanModeNormal(MinerConfigValue):
mode: str = field(init=False, default="normal") mode: str = Field(init=False, default="normal")
minimum_fans: int = 1 minimum_fans: int = 1
minimum_speed: int = 0 minimum_speed: int = 0
@@ -87,9 +88,8 @@ class FanModeNormal(MinerConfigValue):
return {"fanset": {"speed": -1, "min_fans": self.minimum_fans}} return {"fanset": {"speed": -1, "min_fans": self.minimum_fans}}
@dataclass
class FanModeManual(MinerConfigValue): class FanModeManual(MinerConfigValue):
mode: str = field(init=False, default="manual") mode: str = Field(init=False, default="manual")
speed: int = 100 speed: int = 100
minimum_fans: int = 1 minimum_fans: int = 1
@@ -151,9 +151,8 @@ class FanModeManual(MinerConfigValue):
return {"fanset": {"speed": self.speed, "min_fans": self.minimum_fans}} return {"fanset": {"speed": self.speed, "min_fans": self.minimum_fans}}
@dataclass
class FanModeImmersion(MinerConfigValue): class FanModeImmersion(MinerConfigValue):
mode: str = field(init=False, default="immersion") mode: str = Field(init=False, default="immersion")
@classmethod @classmethod
def from_dict(cls, dict_conf: dict | None) -> "FanModeImmersion": def from_dict(cls, dict_conf: dict | None) -> "FanModeImmersion":
@@ -273,7 +272,7 @@ class FanModeConfig(MinerConfigOption):
keys = temperature_conf.keys() keys = temperature_conf.keys()
if "auto" in keys: if "auto" in keys:
if "minimumRequiredFans" in keys: if "minimumRequiredFans" in keys:
return cls.normal(temperature_conf["minimumRequiredFans"]) return cls.normal(minimum_fans=temperature_conf["minimumRequiredFans"])
return cls.normal() return cls.normal()
if "manual" in keys: if "manual" in keys:
conf = {} conf = {}
@@ -300,7 +299,9 @@ class FanModeConfig(MinerConfigOption):
mode = web_config["general-config"]["environment-profile"] mode = web_config["general-config"]["environment-profile"]
if mode == "AirCooling": if mode == "AirCooling":
if web_config["advance-config"]["override-fan-control"]: if web_config["advance-config"]["override-fan-control"]:
return cls.manual(web_config["advance-config"]["fan-fixed-percent"]) return cls.manual(
speed=web_config["advance-config"]["fan-fixed-percent"]
)
return cls.normal() return cls.normal()
return cls.immersion() return cls.immersion()
except LookupError: except LookupError:
@@ -333,3 +334,6 @@ class FanModeConfig(MinerConfigOption):
except LookupError: except LookupError:
pass pass
return cls.default() return cls.default()
FanMode = TypeVar("FanMode", bound=Union[*[v.value for v in FanModeConfig]])

View File

@@ -15,7 +15,8 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import field
from typing import TypeVar, Union
from pyasic import settings from pyasic import settings
from pyasic.config.base import MinerConfigOption, MinerConfigValue from pyasic.config.base import MinerConfigOption, MinerConfigValue
@@ -34,11 +35,10 @@ from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
TunerPerformanceMode, TunerPerformanceMode,
) )
from .algo import TunerAlgo from .algo import TunerAlgo, TunerAlgoType
from .scaling import ScalingConfig from .scaling import ScalingConfig
@dataclass
class MiningModeNormal(MinerConfigValue): class MiningModeNormal(MinerConfigValue):
mode: str = field(init=False, default="normal") mode: str = field(init=False, default="normal")
@@ -74,7 +74,6 @@ class MiningModeNormal(MinerConfigValue):
return {"autotunerset": {"enabled": False}} return {"autotunerset": {"enabled": False}}
@dataclass
class MiningModeSleep(MinerConfigValue): class MiningModeSleep(MinerConfigValue):
mode: str = field(init=False, default="sleep") mode: str = field(init=False, default="sleep")
@@ -107,7 +106,6 @@ class MiningModeSleep(MinerConfigValue):
} }
@dataclass
class MiningModeLPM(MinerConfigValue): class MiningModeLPM(MinerConfigValue):
mode: str = field(init=False, default="low") mode: str = field(init=False, default="low")
@@ -130,7 +128,6 @@ class MiningModeLPM(MinerConfigValue):
return {"settings": {"level": 1}} return {"settings": {"level": 1}}
@dataclass
class MiningModeHPM(MinerConfigValue): class MiningModeHPM(MinerConfigValue):
mode: str = field(init=False, default="high") mode: str = field(init=False, default="high")
@@ -150,12 +147,11 @@ class MiningModeHPM(MinerConfigValue):
return {"mode": {"mode": "turbo"}} return {"mode": {"mode": "turbo"}}
@dataclass
class MiningModePowerTune(MinerConfigValue): class MiningModePowerTune(MinerConfigValue):
mode: str = field(init=False, default="power_tuning") mode: str = field(init=False, default="power_tuning")
power: int = None power: int | None = None
algo: TunerAlgo = field(default_factory=TunerAlgo.default) algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
scaling: ScalingConfig = None scaling: ScalingConfig | None = None
@classmethod @classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune": def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune":
@@ -247,11 +243,10 @@ class MiningModePowerTune(MinerConfigValue):
return {"autotunerset": {"enabled": True}} return {"autotunerset": {"enabled": True}}
@dataclass
class MiningModeHashrateTune(MinerConfigValue): class MiningModeHashrateTune(MinerConfigValue):
mode: str = field(init=False, default="hashrate_tuning") mode: str = field(init=False, default="hashrate_tuning")
hashrate: int = None hashrate: int = None
algo: TunerAlgo = field(default_factory=TunerAlgo.default) algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
scaling: ScalingConfig = None scaling: ScalingConfig = None
@classmethod @classmethod
@@ -343,7 +338,6 @@ class MiningModeHashrateTune(MinerConfigValue):
return {"autotunerset": {"enabled": True}} return {"autotunerset": {"enabled": True}}
@dataclass
class ManualBoardSettings(MinerConfigValue): class ManualBoardSettings(MinerConfigValue):
freq: float freq: float
volt: float volt: float
@@ -358,7 +352,6 @@ class ManualBoardSettings(MinerConfigValue):
return {"miner-mode": 0} return {"miner-mode": 0}
@dataclass
class MiningModeManual(MinerConfigValue): class MiningModeManual(MinerConfigValue):
mode: str = field(init=False, default="manual") mode: str = field(init=False, default="manual")
@@ -521,7 +514,7 @@ class MiningModeConfig(MinerConfigOption):
if autotuning_conf.get("psu_power_limit") is not None: if autotuning_conf.get("psu_power_limit") is not None:
# old autotuning conf # old autotuning conf
return cls.power_tuning( return cls.power_tuning(
autotuning_conf["psu_power_limit"], power=autotuning_conf["psu_power_limit"],
scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"), scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"),
) )
if autotuning_conf.get("mode") is not None: if autotuning_conf.get("mode") is not None:
@@ -530,7 +523,7 @@ class MiningModeConfig(MinerConfigOption):
if mode == "power_target": if mode == "power_target":
if autotuning_conf.get("power_target") is not None: if autotuning_conf.get("power_target") is not None:
return cls.power_tuning( return cls.power_tuning(
autotuning_conf["power_target"], power=autotuning_conf["power_target"],
scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"), scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"),
) )
return cls.power_tuning( return cls.power_tuning(
@@ -539,7 +532,7 @@ class MiningModeConfig(MinerConfigOption):
if mode == "hashrate_target": if mode == "hashrate_target":
if autotuning_conf.get("hashrate_target") is not None: if autotuning_conf.get("hashrate_target") is not None:
return cls.hashrate_tuning( return cls.hashrate_tuning(
autotuning_conf["hashrate_target"], hashrate=autotuning_conf["hashrate_target"],
scaling=ScalingConfig.from_bosminer(toml_conf, mode="hashrate"), scaling=ScalingConfig.from_bosminer(toml_conf, mode="hashrate"),
) )
return cls.hashrate_tuning( return cls.hashrate_tuning(
@@ -556,7 +549,7 @@ class MiningModeConfig(MinerConfigOption):
if mode_settings["preset"] == "disabled": if mode_settings["preset"] == "disabled":
return MiningModeManual.from_vnish(mode_settings) return MiningModeManual.from_vnish(mode_settings)
else: else:
return cls.power_tuning(int(mode_settings["preset"])) return cls.power_tuning(power=int(mode_settings["preset"]))
@classmethod @classmethod
def from_boser(cls, grpc_miner_conf: dict): def from_boser(cls, grpc_miner_conf: dict):
@@ -571,7 +564,7 @@ class MiningModeConfig(MinerConfigOption):
if tuner_conf["tunerMode"] == 1: if tuner_conf["tunerMode"] == 1:
if tuner_conf.get("powerTarget") is not None: if tuner_conf.get("powerTarget") is not None:
return cls.power_tuning( return cls.power_tuning(
tuner_conf["powerTarget"]["watt"], power=tuner_conf["powerTarget"]["watt"],
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"), scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"),
) )
return cls.power_tuning( return cls.power_tuning(
@@ -581,7 +574,7 @@ class MiningModeConfig(MinerConfigOption):
if tuner_conf["tunerMode"] == 2: if tuner_conf["tunerMode"] == 2:
if tuner_conf.get("hashrateTarget") is not None: if tuner_conf.get("hashrateTarget") is not None:
return cls.hashrate_tuning( return cls.hashrate_tuning(
int(tuner_conf["hashrateTarget"]["terahashPerSecond"]), hashrate=int(tuner_conf["hashrateTarget"]["terahashPerSecond"]),
scaling=ScalingConfig.from_boser( scaling=ScalingConfig.from_boser(
grpc_miner_conf, mode="hashrate" grpc_miner_conf, mode="hashrate"
), ),
@@ -592,13 +585,13 @@ class MiningModeConfig(MinerConfigOption):
if tuner_conf.get("powerTarget") is not None: if tuner_conf.get("powerTarget") is not None:
return cls.power_tuning( return cls.power_tuning(
tuner_conf["powerTarget"]["watt"], power=tuner_conf["powerTarget"]["watt"],
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"), scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"),
) )
if tuner_conf.get("hashrateTarget") is not None: if tuner_conf.get("hashrateTarget") is not None:
return cls.hashrate_tuning( return cls.hashrate_tuning(
int(tuner_conf["hashrateTarget"]["terahashPerSecond"]), hashrate=int(tuner_conf["hashrateTarget"]["terahashPerSecond"]),
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="hashrate"), scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="hashrate"),
) )
@@ -617,9 +610,9 @@ class MiningModeConfig(MinerConfigOption):
if mode_data.get("Mode") == "turbo": if mode_data.get("Mode") == "turbo":
return cls.high() return cls.high()
if mode_data.get("Ths") is not None: if mode_data.get("Ths") is not None:
return cls.hashrate_tuning(mode_data["Ths"]) return cls.hashrate_tuning(hashrate=mode_data["Ths"])
if mode_data.get("Power") is not None: if mode_data.get("Power") is not None:
return cls.power_tuning(mode_data["Power"]) return cls.power_tuning(power=mode_data["Power"])
except LookupError: except LookupError:
return cls.default() return cls.default()
@@ -647,3 +640,6 @@ class MiningModeConfig(MinerConfigOption):
except LookupError: except LookupError:
pass pass
return cls.default() return cls.default()
MiningMode = TypeVar("MiningMode", bound=Union[*[v.value for v in MiningModeConfig]])

View File

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

View File

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

View File

@@ -17,9 +17,10 @@ from __future__ import annotations
import random import random
import string import string
from dataclasses import dataclass, field
from typing import List from typing import List
from pydantic import Field
from pyasic.config.base import MinerConfigValue from pyasic.config.base import MinerConfigValue
from pyasic.web.braiins_os.proto.braiins.bos.v1 import ( from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
PoolConfiguration, PoolConfiguration,
@@ -30,7 +31,6 @@ from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
) )
@dataclass
class Pool(MinerConfigValue): class Pool(MinerConfigValue):
url: str url: str
user: str user: str
@@ -235,11 +235,10 @@ class Pool(MinerConfigValue):
) )
@dataclass
class PoolGroup(MinerConfigValue): class PoolGroup(MinerConfigValue):
pools: list[Pool] = field(default_factory=list) pools: list[Pool] = Field(default_factory=list)
quota: int = 1 quota: int = 1
name: str = None name: str | None = None
def __post_init__(self): def __post_init__(self):
if self.name is None: if self.name is None:
@@ -254,7 +253,7 @@ class PoolGroup(MinerConfigValue):
if len(self.pools) > idx: if len(self.pools) > idx:
pools.append(self.pools[idx].as_am_modern(user_suffix=user_suffix)) pools.append(self.pools[idx].as_am_modern(user_suffix=user_suffix))
else: else:
pools.append(Pool("", "", "").as_am_modern()) pools.append(Pool(url="", user="", password="").as_am_modern())
idx += 1 idx += 1
return pools return pools
@@ -267,7 +266,7 @@ class PoolGroup(MinerConfigValue):
**self.pools[idx].as_wm(idx=idx + 1, user_suffix=user_suffix) **self.pools[idx].as_wm(idx=idx + 1, user_suffix=user_suffix)
) )
else: else:
pools.update(**Pool("", "", "").as_wm(idx=idx + 1)) pools.update(**Pool(url="", user="", password="").as_wm(idx=idx + 1))
idx += 1 idx += 1
return pools return pools
@@ -280,7 +279,9 @@ class PoolGroup(MinerConfigValue):
**self.pools[idx].as_am_old(idx=idx + 1, user_suffix=user_suffix) **self.pools[idx].as_am_old(idx=idx + 1, user_suffix=user_suffix)
) )
else: else:
pools.update(**Pool("", "", "").as_am_old(idx=idx + 1)) pools.update(
**Pool(url="", user="", password="").as_am_old(idx=idx + 1)
)
idx += 1 idx += 1
return pools return pools
@@ -290,7 +291,7 @@ class PoolGroup(MinerConfigValue):
def as_avalon(self, user_suffix: str = None) -> str: def as_avalon(self, user_suffix: str = None) -> str:
if len(self.pools) > 0: if len(self.pools) > 0:
return self.pools[0].as_avalon(user_suffix=user_suffix) return self.pools[0].as_avalon(user_suffix=user_suffix)
return Pool("", "", "").as_avalon() return Pool(url="", user="", password="").as_avalon()
def as_inno(self, user_suffix: str = None) -> dict: def as_inno(self, user_suffix: str = None) -> dict:
pools = {} pools = {}
@@ -301,7 +302,7 @@ class PoolGroup(MinerConfigValue):
**self.pools[idx].as_inno(idx=idx + 1, user_suffix=user_suffix) **self.pools[idx].as_inno(idx=idx + 1, user_suffix=user_suffix)
) )
else: else:
pools.update(**Pool("", "", "").as_inno(idx=idx + 1)) pools.update(**Pool(url="", user="", password="").as_inno(idx=idx + 1))
idx += 1 idx += 1
return pools return pools
@@ -371,11 +372,11 @@ class PoolGroup(MinerConfigValue):
@classmethod @classmethod
def from_goldshell(cls, web_pools: list) -> "PoolGroup": def from_goldshell(cls, web_pools: list) -> "PoolGroup":
return cls([Pool.from_goldshell(p) for p in web_pools]) return cls(pools=[Pool.from_goldshell(p) for p in web_pools])
@classmethod @classmethod
def from_inno(cls, web_pools: list) -> "PoolGroup": def from_inno(cls, web_pools: list) -> "PoolGroup":
return cls([Pool.from_inno(p) for p in web_pools]) return cls(pools=[Pool.from_inno(p) for p in web_pools])
@classmethod @classmethod
def from_bosminer(cls, toml_group_conf: dict) -> "PoolGroup": def from_bosminer(cls, toml_group_conf: dict) -> "PoolGroup":
@@ -389,7 +390,7 @@ class PoolGroup(MinerConfigValue):
@classmethod @classmethod
def from_vnish(cls, web_settings_pools: dict) -> "PoolGroup": def from_vnish(cls, web_settings_pools: dict) -> "PoolGroup":
return cls([Pool.from_vnish(p) for p in web_settings_pools]) return cls(pools=[Pool.from_vnish(p) for p in web_settings_pools])
@classmethod @classmethod
def from_boser(cls, grpc_pool_group: dict) -> "PoolGroup": def from_boser(cls, grpc_pool_group: dict) -> "PoolGroup":
@@ -424,9 +425,8 @@ class PoolGroup(MinerConfigValue):
) )
@dataclass
class PoolConfig(MinerConfigValue): class PoolConfig(MinerConfigValue):
groups: List[PoolGroup] = field(default_factory=list) groups: List[PoolGroup] = Field(default_factory=list)
@classmethod @classmethod
def default(cls) -> "PoolConfig": def default(cls) -> "PoolConfig":
@@ -538,38 +538,38 @@ class PoolConfig(MinerConfigValue):
return PoolConfig.default() return PoolConfig.default()
pool_data = sorted(pool_data, key=lambda x: int(x["POOL"])) pool_data = sorted(pool_data, key=lambda x: int(x["POOL"]))
return cls([PoolGroup.from_api(pool_data)]) return cls(groups=[PoolGroup.from_api(pool_data)])
@classmethod @classmethod
def from_epic(cls, web_conf: dict) -> "PoolConfig": def from_epic(cls, web_conf: dict) -> "PoolConfig":
pool_data = web_conf["StratumConfigs"] pool_data = web_conf["StratumConfigs"]
return cls([PoolGroup.from_epic(pool_data)]) return cls(groups=[PoolGroup.from_epic(pool_data)])
@classmethod @classmethod
def from_am_modern(cls, web_conf: dict) -> "PoolConfig": def from_am_modern(cls, web_conf: dict) -> "PoolConfig":
pool_data = web_conf["pools"] pool_data = web_conf["pools"]
return cls([PoolGroup.from_am_modern(pool_data)]) return cls(groups=[PoolGroup.from_am_modern(pool_data)])
@classmethod @classmethod
def from_goldshell(cls, web_pools: list) -> "PoolConfig": def from_goldshell(cls, web_pools: list) -> "PoolConfig":
return cls([PoolGroup.from_goldshell(web_pools)]) return cls(groups=[PoolGroup.from_goldshell(web_pools)])
@classmethod @classmethod
def from_inno(cls, web_pools: list) -> "PoolConfig": def from_inno(cls, web_pools: list) -> "PoolConfig":
return cls([PoolGroup.from_inno(web_pools)]) return cls(groups=[PoolGroup.from_inno(web_pools)])
@classmethod @classmethod
def from_bosminer(cls, toml_conf: dict) -> "PoolConfig": def from_bosminer(cls, toml_conf: dict) -> "PoolConfig":
if toml_conf.get("group") is None: if toml_conf.get("group") is None:
return cls() return cls()
return cls([PoolGroup.from_bosminer(g) for g in toml_conf["group"]]) return cls(groups=[PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
@classmethod @classmethod
def from_vnish(cls, web_settings: dict) -> "PoolConfig": def from_vnish(cls, web_settings: dict) -> "PoolConfig":
try: try:
return cls([PoolGroup.from_vnish(web_settings["miner"]["pools"])]) return cls(groups=[PoolGroup.from_vnish(web_settings["miner"]["pools"])])
except LookupError: except LookupError:
return cls() return cls()

View File

@@ -20,11 +20,10 @@ from dataclasses import dataclass
from pyasic.config.base import MinerConfigValue from pyasic.config.base import MinerConfigValue
@dataclass
class TemperatureConfig(MinerConfigValue): class TemperatureConfig(MinerConfigValue):
target: int = None target: int | None = None
hot: int = None hot: int | None = None
danger: int = None danger: int | None = None
@classmethod @classmethod
def default(cls): def default(cls):

View File

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

View File

@@ -14,14 +14,14 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from dataclasses import dataclass
from typing import Any from typing import Any
from .hashrate import AlgoHashRate from pydantic import BaseModel, field_serializer
from .hashrate import AlgoHashRateType
@dataclass class HashBoard(BaseModel):
class HashBoard:
"""A Dataclass to standardize hashboard data. """A Dataclass to standardize hashboard data.
Attributes: Attributes:
@@ -39,16 +39,21 @@ class HashBoard:
""" """
slot: int = 0 slot: int = 0
hashrate: AlgoHashRate = None hashrate: AlgoHashRateType | None = None
temp: int = None temp: int | None = None
chip_temp: int = None chip_temp: int | None = None
chips: int = None chips: int | None = None
expected_chips: int = None expected_chips: int | None = None
serial_number: str = None serial_number: str | None = None
missing: bool = True missing: bool = True
tuned: bool = None tuned: bool | None = None
active: bool = None active: bool | None = None
voltage: float = None voltage: float | None = None
@field_serializer("hashrate")
def serialize_hashrate(self, hashrate: AlgoHashRateType | None) -> float:
if hashrate is not None:
return float(hashrate)
def get(self, __key: str, default: Any = None): def get(self, __key: str, default: Any = None):
try: try:

View File

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

View File

@@ -13,12 +13,10 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.data.error_codes.base import BaseMinerError
from dataclasses import asdict, dataclass, fields
@dataclass class X19Error(BaseMinerError):
class X19Error:
"""A Dataclass to handle error codes of X19 miners. """A Dataclass to handle error codes of X19 miners.
Attributes: Attributes:
@@ -28,10 +26,3 @@ class X19Error:
error_message: str error_message: str
error_code: int = 0 error_code: int = 0
@classmethod
def fields(cls):
return fields(cls)
def asdict(self):
return asdict(self)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
from enum import Enum from enum import Enum
from pyasic.data.hashrate.base import AlgoHashRateType
from pyasic.data.hashrate.sha256 import SHA256HashRate from pyasic.data.hashrate.sha256 import SHA256HashRate
from pyasic.device.algorithm.sha256 import SHA256Unit from pyasic.device.algorithm.sha256 import SHA256Unit

View File

@@ -0,0 +1,5 @@
from pydantic import BaseModel
class AlgoHashRateType(BaseModel):
pass

View File

@@ -1,13 +1,11 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from pyasic.data.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm import MinerAlgo from pyasic.device.algorithm import MinerAlgo
from pyasic.device.algorithm.sha256 import SHA256Unit from pyasic.device.algorithm.sha256 import SHA256Unit
@dataclass class SHA256HashRate(AlgoHashRateType):
class SHA256HashRate:
rate: float rate: float
unit: SHA256Unit = MinerAlgo.SHA256.unit.default unit: SHA256Unit = MinerAlgo.SHA256.unit.default
@@ -25,28 +23,38 @@ class SHA256HashRate:
def __add__(self, other: SHA256HashRate | int | float) -> SHA256HashRate: def __add__(self, other: SHA256HashRate | int | float) -> SHA256HashRate:
if isinstance(other, SHA256HashRate): if isinstance(other, SHA256HashRate):
return SHA256HashRate(self.rate + other.into(self.unit).rate, self.unit) return SHA256HashRate(
return SHA256HashRate(self.rate + other, self.unit) rate=self.rate + other.into(self.unit).rate, unit=self.unit
)
return SHA256HashRate(rate=self.rate + other, unit=self.unit)
def __sub__(self, other: SHA256HashRate | int | float) -> SHA256HashRate: def __sub__(self, other: SHA256HashRate | int | float) -> SHA256HashRate:
if isinstance(other, SHA256HashRate): if isinstance(other, SHA256HashRate):
return SHA256HashRate(self.rate - other.into(self.unit).rate, self.unit) return SHA256HashRate(
return SHA256HashRate(self.rate - other, self.unit) rate=self.rate - other.into(self.unit).rate, unit=self.unit
)
return SHA256HashRate(rate=self.rate - other, unit=self.unit)
def __truediv__(self, other: SHA256HashRate | int | float): def __truediv__(self, other: SHA256HashRate | int | float):
if isinstance(other, SHA256HashRate): if isinstance(other, SHA256HashRate):
return SHA256HashRate(self.rate / other.into(self.unit).rate, self.unit) return SHA256HashRate(
return SHA256HashRate(self.rate / other, self.unit) rate=self.rate / other.into(self.unit).rate, unit=self.unit
)
return SHA256HashRate(rate=self.rate / other, unit=self.unit)
def __floordiv__(self, other: SHA256HashRate | int | float): def __floordiv__(self, other: SHA256HashRate | int | float):
if isinstance(other, SHA256HashRate): if isinstance(other, SHA256HashRate):
return SHA256HashRate(self.rate // other.into(self.unit).rate, self.unit) return SHA256HashRate(
return SHA256HashRate(self.rate // other, self.unit) rate=self.rate // other.into(self.unit).rate, unit=self.unit
)
return SHA256HashRate(rate=self.rate // other, unit=self.unit)
def __mul__(self, other: SHA256HashRate | int | float): def __mul__(self, other: SHA256HashRate | int | float):
if isinstance(other, SHA256HashRate): if isinstance(other, SHA256HashRate):
return SHA256HashRate(self.rate * other.into(self.unit).rate, self.unit) return SHA256HashRate(
return SHA256HashRate(self.rate * other, self.unit) rate=self.rate * other.into(self.unit).rate, unit=self.unit
)
return SHA256HashRate(rate=self.rate * other, unit=self.unit)
def into(self, other: SHA256Unit) -> SHA256HashRate: def into(self, other: SHA256Unit) -> SHA256HashRate:
return SHA256HashRate( return SHA256HashRate(

View File

@@ -1,8 +1,9 @@
from dataclasses import dataclass, field
from enum import Enum from enum import Enum
from typing import Optional from typing import Optional
from urllib.parse import urlparse from urllib.parse import urlparse
from pydantic import BaseModel, computed_field, model_serializer
class Scheme(Enum): class Scheme(Enum):
STRATUM_V1 = "stratum+tcp" STRATUM_V1 = "stratum+tcp"
@@ -10,13 +11,16 @@ class Scheme(Enum):
STRATUM_V1_SSL = "stratum+ssl" STRATUM_V1_SSL = "stratum+ssl"
@dataclass class PoolUrl(BaseModel):
class PoolUrl:
scheme: Scheme scheme: Scheme
host: str host: str
port: int port: int
pubkey: Optional[str] = None pubkey: Optional[str] = None
@model_serializer
def serialize(self):
return str(self)
def __str__(self) -> str: def __str__(self) -> str:
if self.scheme == Scheme.STRATUM_V2 and self.pubkey: if self.scheme == Scheme.STRATUM_V2 and self.pubkey:
return f"{self.scheme.value}://{self.host}:{self.port}/{self.pubkey}" return f"{self.scheme.value}://{self.host}:{self.port}/{self.pubkey}"
@@ -36,8 +40,7 @@ class PoolUrl:
return cls(scheme=scheme, host=host, port=port, pubkey=pubkey) return cls(scheme=scheme, host=host, port=port, pubkey=pubkey)
@dataclass class PoolMetrics(BaseModel):
class PoolMetrics:
"""A dataclass to standardize pool metrics returned from miners. """A dataclass to standardize pool metrics returned from miners.
Attributes: Attributes:
@@ -63,18 +66,14 @@ class PoolMetrics:
alive: bool = None alive: bool = None
index: int = None index: int = None
user: str = None user: str = None
pool_rejected_percent: float = field(init=False)
pool_stale_percent: float = field(init=False)
@computed_field # type: ignore[misc]
@property @property
def pool_rejected_percent(self) -> float: # noqa - Skip PyCharm inspection def pool_rejected_percent(self) -> float: # noqa - Skip PyCharm inspection
"""Calculate and return the percentage of rejected shares""" """Calculate and return the percentage of rejected shares"""
return self._calculate_percentage(self.rejected, self.accepted + self.rejected) return self._calculate_percentage(self.rejected, self.accepted + self.rejected)
@pool_rejected_percent.setter @computed_field # type: ignore[misc]
def pool_rejected_percent(self, val):
pass
@property @property
def pool_stale_percent(self) -> float: # noqa - Skip PyCharm inspection def pool_stale_percent(self) -> float: # noqa - Skip PyCharm inspection
"""Calculate and return the percentage of stale shares.""" """Calculate and return the percentage of stale shares."""
@@ -82,10 +81,6 @@ class PoolMetrics:
self.get_failures, self.accepted + self.rejected self.get_failures, self.accepted + self.rejected
) )
@pool_stale_percent.setter
def pool_stale_percent(self, val):
pass
@staticmethod @staticmethod
def _calculate_percentage(value: int, total: int) -> float: def _calculate_percentage(value: int, total: int) -> float:
"""Calculate the percentage.""" """Calculate the percentage."""

View File

@@ -1,3 +1,4 @@
from pyasic.device.algorithm.base import MinerAlgoType
from pyasic.device.algorithm.sha256 import SHA256Algo from pyasic.device.algorithm.sha256 import SHA256Algo

View File

@@ -0,0 +1,7 @@
from __future__ import annotations
from enum import IntEnum
class MinerAlgoType(str):
pass

View File

@@ -2,6 +2,8 @@ from __future__ import annotations
from enum import IntEnum from enum import IntEnum
from .base import MinerAlgoType
class SHA256Unit(IntEnum): class SHA256Unit(IntEnum):
H = 1 H = 1
@@ -58,7 +60,7 @@ class SHA256Unit(IntEnum):
# make this json serializable # make this json serializable
class _SHA256Algo(str): class _SHA256Algo(MinerAlgoType):
unit = SHA256Unit unit = SHA256Unit
def __repr__(self): def __repr__(self):

View File

@@ -1,7 +1,11 @@
from enum import Enum from enum import Enum
class AntminerModels(str, Enum): class MinerModelType(str, Enum):
pass
class AntminerModels(MinerModelType):
D3 = "D3" D3 = "D3"
HS3 = "HS3" HS3 = "HS3"
L3Plus = "L3+" L3Plus = "L3+"
@@ -58,7 +62,7 @@ class AntminerModels(str, Enum):
return self.value return self.value
class WhatsminerModels(str, Enum): class WhatsminerModels(MinerModelType):
M20V10 = "M20 V10" M20V10 = "M20 V10"
M20SV10 = "M20S V10" M20SV10 = "M20S V10"
M20SV20 = "M20S V20" M20SV20 = "M20S V20"
@@ -279,7 +283,7 @@ class WhatsminerModels(str, Enum):
return self.value return self.value
class AvalonminerModels(str, Enum): class AvalonminerModels(MinerModelType):
Avalon721 = "Avalon 721" Avalon721 = "Avalon 721"
Avalon741 = "Avalon 741" Avalon741 = "Avalon 741"
Avalon761 = "Avalon 761" Avalon761 = "Avalon 761"
@@ -298,7 +302,7 @@ class AvalonminerModels(str, Enum):
return self.value return self.value
class InnosiliconModels(str, Enum): class InnosiliconModels(MinerModelType):
T3HPlus = "T3H+" T3HPlus = "T3H+"
A10X = "A10X" A10X = "A10X"
A11 = "A11" A11 = "A11"
@@ -308,7 +312,7 @@ class InnosiliconModels(str, Enum):
return self.value return self.value
class GoldshellModels(str, Enum): class GoldshellModels(MinerModelType):
CK5 = "CK5" CK5 = "CK5"
HS5 = "HS5" HS5 = "HS5"
KD5 = "KD5" KD5 = "KD5"
@@ -320,7 +324,7 @@ class GoldshellModels(str, Enum):
return self.value return self.value
class ePICModels(str, Enum): class ePICModels(MinerModelType):
BM520i = "BlockMiner 520i" BM520i = "BlockMiner 520i"
BM720i = "BlockMiner 720i" BM720i = "BlockMiner 720i"
@@ -328,7 +332,7 @@ class ePICModels(str, Enum):
return self.value return self.value
class AuradineModels(str, Enum): class AuradineModels(MinerModelType):
AT1500 = "AT1500" AT1500 = "AT1500"
AT2860 = "AT2860" AT2860 = "AT2860"
AT2880 = "AT2880" AT2880 = "AT2880"
@@ -341,7 +345,7 @@ class AuradineModels(str, Enum):
return self.value return self.value
class BitAxeModels(str, Enum): class BitAxeModels(MinerModelType):
BM1366 = "Ultra" BM1366 = "Ultra"
BM1368 = "Supra" BM1368 = "Supra"
BM1397 = "Max" BM1397 = "Max"
@@ -351,7 +355,7 @@ class BitAxeModels(str, Enum):
return self.value return self.value
class IceRiverModels(str, Enum): class IceRiverModels(MinerModelType):
KS0 = "KS0" KS0 = "KS0"
KS1 = "KS1" KS1 = "KS1"
KS2 = "KS2" KS2 = "KS2"
@@ -366,7 +370,7 @@ class IceRiverModels(str, Enum):
return self.value return self.value
class HammerModels(str, Enum): class HammerModels(MinerModelType):
D10 = "D10" D10 = "D10"
def __str__(self): def __str__(self):

View File

@@ -126,7 +126,7 @@ class HiveonT9(Hiveon, T9):
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
hashboards[board].hashrate = AlgoHashRate.SHA256( hashboards[board].hashrate = AlgoHashRate.SHA256(
hashrate, HashUnit.SHA256.GH rate=float(hashrate), unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[board].chips = chips hashboards[board].chips = chips
@@ -172,4 +172,4 @@ class HiveonT9(Hiveon, T9):
pass pass
if not env_temp_list == []: if not env_temp_list == []:
return round(float(sum(env_temp_list) / len(env_temp_list)), 2) return round(sum(env_temp_list) / len(env_temp_list))

View File

@@ -183,13 +183,13 @@ class AntminerModern(BMMiner):
async def stop_mining(self) -> bool: async def stop_mining(self) -> bool:
cfg = await self.get_config() cfg = await self.get_config()
cfg.miner_mode = MiningModeConfig.sleep cfg.miner_mode = MiningModeConfig.sleep()
await self.send_config(cfg) await self.send_config(cfg)
return True return True
async def resume_mining(self) -> bool: async def resume_mining(self) -> bool:
cfg = await self.get_config() cfg = await self.get_config()
cfg.miner_mode = MiningModeConfig.normal cfg.miner_mode = MiningModeConfig.normal()
await self.send_config(cfg) await self.send_config(cfg)
return True return True
@@ -239,7 +239,7 @@ class AntminerModern(BMMiner):
for item in web_summary["SUMMARY"][0]["status"]: for item in web_summary["SUMMARY"][0]["status"]:
try: try:
if not item["status"] == "s": if not item["status"] == "s":
errors.append(X19Error(item["msg"])) errors.append(X19Error(error_message=item["msg"]))
except KeyError: except KeyError:
continue continue
except LookupError: except LookupError:
@@ -248,7 +248,7 @@ class AntminerModern(BMMiner):
async def _get_hashboards(self) -> List[HashBoard]: async def _get_hashboards(self) -> List[HashBoard]:
hashboards = [ hashboards = [
HashBoard(idx, expected_chips=self.expected_chips) HashBoard(slot=idx, expected_chips=self.expected_chips)
for idx in range(self.expected_hashboards) for idx in range(self.expected_hashboards)
] ]
@@ -261,7 +261,7 @@ class AntminerModern(BMMiner):
try: try:
for board in rpc_stats["STATS"][0]["chain"]: for board in rpc_stats["STATS"][0]["chain"]:
hashboards[board["index"]].hashrate = AlgoHashRate.SHA256( hashboards[board["index"]].hashrate = AlgoHashRate.SHA256(
board["rate_real"], HashUnit.SHA256.GH rate=board["rate_real"], unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[board["index"]].chips = board["asic_num"] hashboards[board["index"]].chips = board["asic_num"]
board_temp_data = list( board_temp_data = list(
@@ -318,7 +318,7 @@ class AntminerModern(BMMiner):
except KeyError: except KeyError:
rate_unit = "GH" rate_unit = "GH"
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
expected_rate, HashUnit.SHA256.from_str(rate_unit) rate=float(expected_rate), unit=HashUnit.SHA256.from_str(rate_unit)
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass
@@ -625,7 +625,7 @@ class AntminerOld(CGMiner):
hashrate = boards[1].get(f"chain_rate{i}") hashrate = boards[1].get(f"chain_rate{i}")
if hashrate: if hashrate:
hashboard.hashrate = AlgoHashRate.SHA256( hashboard.hashrate = AlgoHashRate.SHA256(
float(hashrate), HashUnit.SHA256.GH rate=float(hashrate), unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
chips = boards[1].get(f"chain_acn{i}") chips = boards[1].get(f"chain_acn{i}")

View File

@@ -293,7 +293,8 @@ class Auradine(StockFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["MHS 5s"], HashUnit.SHA256.MH rate=float(rpc_summary["SUMMARY"][0]["MHS 5s"]),
unit=HashUnit.SHA256.MH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -322,9 +323,9 @@ class Auradine(StockFirmware):
for board in rpc_devs["DEVS"]: for board in rpc_devs["DEVS"]:
b_id = board["ID"] - 1 b_id = board["ID"] - 1
hashboards[b_id].hashrate = AlgoHashRate.SHA256( hashboards[b_id].hashrate = AlgoHashRate.SHA256(
board["MHS 5s"], HashUnit.SHA256.MH rate=float(board["MHS 5s"]), unit=HashUnit.SHA256.MH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[b_id].temp = round(float(float(board["Temperature"])), 2) hashboards[b_id].temp = round(float(board["Temperature"]))
hashboards[b_id].missing = False hashboards[b_id].missing = False
except LookupError: except LookupError:
pass pass
@@ -390,7 +391,7 @@ class Auradine(StockFirmware):
if web_fan is not None: if web_fan is not None:
try: try:
for fan in web_fan["Fan"]: for fan in web_fan["Fan"]:
fans.append(Fan(round(fan["Speed"]))) fans.append(Fan(speed=round(fan["Speed"])))
except LookupError: except LookupError:
pass pass
return fans return fans

View File

@@ -184,7 +184,7 @@ class AvalonMiner(CGMiner):
if rpc_devs is not None: if rpc_devs is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
rpc_devs["DEVS"][0]["MHS 1m"], HashUnit.SHA256.MH rate=float(rpc_devs["DEVS"][0]["MHS 1m"]), unit=HashUnit.SHA256.MH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
pass pass
@@ -217,7 +217,7 @@ class AvalonMiner(CGMiner):
try: try:
board_hr = parsed_stats["MGHS"][board] board_hr = parsed_stats["MGHS"][board]
hashboards[board].hashrate = AlgoHashRate.SHA256( hashboards[board].hashrate = AlgoHashRate.SHA256(
float(board_hr), HashUnit.SHA256.GH rate=float(board_hr), unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass
@@ -253,7 +253,7 @@ class AvalonMiner(CGMiner):
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"] unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
float(parsed_stats["GHSmm"][0]), HashUnit.SHA256.GH rate=float(parsed_stats["GHSmm"][0]), unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
pass pass

View File

@@ -121,7 +121,8 @@ class BFGMiner(StockFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["MHS 20s"], HashUnit.SHA256.MH rate=float(rpc_summary["SUMMARY"][0]["MHS 20s"]),
unit=HashUnit.SHA256.MH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -167,7 +168,7 @@ class BFGMiner(StockFirmware):
hashrate = boards[1].get(f"chain_rate{i}") hashrate = boards[1].get(f"chain_rate{i}")
if hashrate: if hashrate:
hashboard.hashrate = AlgoHashRate.SHA256( hashboard.hashrate = AlgoHashRate.SHA256(
hashrate, HashUnit.SHA256.GH rate=float(hashrate), unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
chips = boards[1].get(f"chain_acn{i}") chips = boards[1].get(f"chain_acn{i}")
@@ -260,7 +261,7 @@ class BFGMiner(StockFirmware):
except KeyError: except KeyError:
rate_unit = "GH" rate_unit = "GH"
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
expected_rate, HashUnit.SHA256.from_str(rate_unit) rate=float(expected_rate), unit=HashUnit.SHA256.from_str(rate_unit)
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -94,7 +94,7 @@ class BitAxe(BaseMiner):
if web_system_info is not None: if web_system_info is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
web_system_info["hashRate"], HashUnit.SHA256.GH rate=float(web_system_info["hashRate"]), unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except KeyError: except KeyError:
pass pass
@@ -124,7 +124,8 @@ class BitAxe(BaseMiner):
return [ return [
HashBoard( HashBoard(
hashrate=AlgoHashRate.SHA256( hashrate=AlgoHashRate.SHA256(
web_system_info["hashRate"], HashUnit.SHA256.GH rate=float(web_system_info["hashRate"]),
unit=HashUnit.SHA256.GH,
).into(self.algo.unit.default), ).into(self.algo.unit.default),
chip_temp=web_system_info.get("temp"), chip_temp=web_system_info.get("temp"),
temp=web_system_info.get("vrTemp"), temp=web_system_info.get("vrTemp"),

View File

@@ -125,7 +125,8 @@ class BMMiner(StockFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
unit=HashUnit.SHA256.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -184,7 +185,7 @@ class BMMiner(StockFirmware):
hashrate = boards[1].get(f"chain_rate{i}") hashrate = boards[1].get(f"chain_rate{i}")
if hashrate: if hashrate:
hashboard.hashrate = AlgoHashRate.SHA256( hashboard.hashrate = AlgoHashRate.SHA256(
hashrate, HashUnit.SHA256.GH rate=float(hashrate), unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
chips = boards[1].get(f"chain_acn{i}") chips = boards[1].get(f"chain_acn{i}")
@@ -246,7 +247,7 @@ class BMMiner(StockFirmware):
except KeyError: except KeyError:
rate_unit = "GH" rate_unit = "GH"
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
expected_rate, HashUnit.SHA256.from_str(rate_unit) rate=float(expected_rate), unit=HashUnit.SHA256.from_str(rate_unit)
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -363,7 +363,8 @@ class BOSMiner(BraiinsOSFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["MHS 1m"], HashUnit.SHA256.MH rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
unit=HashUnit.SHA256.MH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
pass pass
@@ -435,7 +436,7 @@ class BOSMiner(BraiinsOSFirmware):
for board in rpc_devs["DEVS"]: for board in rpc_devs["DEVS"]:
_id = board["ID"] - offset _id = board["ID"] - offset
hashboards[_id].hashrate = AlgoHashRate.SHA256( hashboards[_id].hashrate = AlgoHashRate.SHA256(
board["MHS 1m"], HashUnit.SHA256.MH rate=float(board["MHS 1m"]), unit=HashUnit.SHA256.MH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
@@ -481,7 +482,7 @@ class BOSMiner(BraiinsOSFirmware):
fans = [] fans = []
for n in range(self.expected_fans): for n in range(self.expected_fans):
try: try:
fans.append(Fan(rpc_fans["FANS"][n]["RPM"])) fans.append(Fan(speed=rpc_fans["FANS"][n]["RPM"]))
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
return fans return fans
@@ -512,7 +513,9 @@ class BOSMiner(BraiinsOSFirmware):
]: ]:
_error = board["Status"].split(" {")[0] _error = board["Status"].split(" {")[0]
_error = _error[0].lower() + _error[1:] _error = _error[0].lower() + _error[1:]
errors.append(BraiinsOSError(f"Slot {_id} {_error}")) errors.append(
BraiinsOSError(error_message=f"Slot {_id} {_error}")
)
return errors return errors
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
@@ -543,16 +546,19 @@ class BOSMiner(BraiinsOSFirmware):
hr_list = [] hr_list = []
for board in rpc_devs["DEVS"]: for board in rpc_devs["DEVS"]:
expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2) expected_hashrate = float(board["Nominal MHS"] / 1000000)
if expected_hashrate: if expected_hashrate:
hr_list.append(expected_hashrate) hr_list.append(expected_hashrate)
if len(hr_list) == 0: if len(hr_list) == 0:
return AlgoHashRate.SHA256(0) return AlgoHashRate.SHA256(rate=float(0))
else: else:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
(sum(hr_list) / len(hr_list)) * self.expected_hashboards rate=float(
) (sum(hr_list) / len(hr_list)) * self.expected_hashboards
),
unit=HashUnit.SHA256.MH,
).into(self.algo.unit.default)
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
@@ -890,7 +896,8 @@ class BOSer(BraiinsOSFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["MHS 1m"], HashUnit.SHA256.MH rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
unit=HashUnit.SHA256.MH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
pass pass
@@ -907,8 +914,10 @@ class BOSer(BraiinsOSFirmware):
if grpc_miner_details is not None: if grpc_miner_details is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
grpc_miner_details["stickerHashrate"]["gigahashPerSecond"], rate=float(
HashUnit.SHA256.GH, grpc_miner_details["stickerHashrate"]["gigahashPerSecond"]
),
unit=HashUnit.SHA256.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass
@@ -933,18 +942,20 @@ class BOSer(BraiinsOSFirmware):
if board.get("chipsCount") is not None: if board.get("chipsCount") is not None:
hashboards[idx].chips = board["chipsCount"] hashboards[idx].chips = board["chipsCount"]
if board.get("boardTemp") is not None: if board.get("boardTemp") is not None:
hashboards[idx].temp = board["boardTemp"]["degreeC"] hashboards[idx].temp = int(board["boardTemp"]["degreeC"])
if board.get("highestChipTemp") is not None: if board.get("highestChipTemp") is not None:
hashboards[idx].chip_temp = board["highestChipTemp"]["temperature"][ hashboards[idx].chip_temp = int(
"degreeC" board["highestChipTemp"]["temperature"]["degreeC"]
] )
if board.get("stats") is not None: if board.get("stats") is not None:
if not board["stats"]["realHashrate"]["last5S"] == {}: if not board["stats"]["realHashrate"]["last5S"] == {}:
hashboards[idx].hashrate = AlgoHashRate.SHA256( hashboards[idx].hashrate = AlgoHashRate.SHA256(
board["stats"]["realHashrate"]["last5S"][ rate=float(
"gigahashPerSecond" board["stats"]["realHashrate"]["last5S"][
], "gigahashPerSecond"
HashUnit.SHA256.GH, ]
),
unit=HashUnit.SHA256.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[idx].missing = False hashboards[idx].missing = False
@@ -993,7 +1004,7 @@ class BOSer(BraiinsOSFirmware):
fans = [] fans = []
for n in range(self.expected_fans): for n in range(self.expected_fans):
try: try:
fans.append(Fan(grpc_cooling_state["fans"][n]["rpm"])) fans.append(Fan(speed=grpc_cooling_state["fans"][n]["rpm"]))
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
return fans return fans
@@ -1024,7 +1035,9 @@ class BOSer(BraiinsOSFirmware):
]: ]:
_error = board["Status"].split(" {")[0] _error = board["Status"].split(" {")[0]
_error = _error[0].lower() + _error[1:] _error = _error[0].lower() + _error[1:]
errors.append(BraiinsOSError(f"Slot {_id} {_error}")) errors.append(
BraiinsOSError(error_message=f"Slot {_id} {_error}")
)
return errors return errors
except LookupError: except LookupError:
pass pass
@@ -1085,7 +1098,7 @@ class BOSer(BraiinsOSFirmware):
for group in grpc_pool_groups["poolGroups"]: for group in grpc_pool_groups["poolGroups"]:
for idx, pool_info in enumerate(group["pools"]): for idx, pool_info in enumerate(group["pools"]):
pool_data = PoolMetrics( pool_data = PoolMetrics(
url=pool_info["url"], url=PoolUrl.from_str(pool_info["url"]),
user=pool_info["user"], user=pool_info["user"],
index=idx, index=idx,
accepted=pool_info["stats"].get("acceptedShares", 0), accepted=pool_info["stats"].get("acceptedShares", 0),

View File

@@ -274,7 +274,7 @@ class BTMiner(StockFirmware):
cfg.mining_mode = MiningModeConfig.normal() cfg.mining_mode = MiningModeConfig.normal()
return cfg return cfg
cfg.mining_mode = MiningModeConfig.power_tuning(power_lim) cfg.mining_mode = MiningModeConfig.power_tuning(power=power_lim)
self.config = cfg self.config = cfg
return self.config return self.config
@@ -404,7 +404,8 @@ class BTMiner(StockFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["MHS 1m"], HashUnit.SHA256.MH rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
unit=HashUnit.SHA256.MH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass
@@ -434,7 +435,7 @@ class BTMiner(StockFirmware):
hashboards[board["ASC"]].chip_temp = round(board["Chip Temp Avg"]) hashboards[board["ASC"]].chip_temp = round(board["Chip Temp Avg"])
hashboards[board["ASC"]].temp = round(board["Temperature"]) hashboards[board["ASC"]].temp = round(board["Temperature"])
hashboards[board["ASC"]].hashrate = AlgoHashRate.SHA256( hashboards[board["ASC"]].hashrate = AlgoHashRate.SHA256(
board["MHS 1m"], HashUnit.SHA256.MH rate=float(board["MHS 1m"]), unit=HashUnit.SHA256.MH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[board["ASC"]].chips = board["Effective Chips"] hashboards[board["ASC"]].chips = board["Effective Chips"]
hashboards[board["ASC"]].serial_number = board["PCB SN"] hashboards[board["ASC"]].serial_number = board["PCB SN"]
@@ -498,8 +499,8 @@ class BTMiner(StockFirmware):
try: try:
if self.expected_fans > 0: if self.expected_fans > 0:
fans = [ fans = [
Fan(rpc_summary["SUMMARY"][0].get("Fan Speed In", 0)), Fan(speed=rpc_summary["SUMMARY"][0].get("Fan Speed In", 0)),
Fan(rpc_summary["SUMMARY"][0].get("Fan Speed Out", 0)), Fan(speed=rpc_summary["SUMMARY"][0].get("Fan Speed Out", 0)),
] ]
except LookupError: except LookupError:
pass pass
@@ -584,7 +585,7 @@ class BTMiner(StockFirmware):
expected_hashrate = rpc_summary["SUMMARY"][0]["Factory GHS"] expected_hashrate = rpc_summary["SUMMARY"][0]["Factory GHS"]
if expected_hashrate: if expected_hashrate:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
expected_hashrate, HashUnit.SHA256.GH rate=float(expected_hashrate), unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:

View File

@@ -124,7 +124,8 @@ class CGMiner(StockFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
unit=HashUnit.SHA256.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass

View File

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

View File

@@ -163,7 +163,7 @@ class GoldshellMiner(BFGMiner):
try: try:
b_id = board["ID"] b_id = board["ID"]
hashboards[b_id].hashrate = AlgoHashRate.SHA256( hashboards[b_id].hashrate = AlgoHashRate.SHA256(
board["MHS 20s"], HashUnit.SHA256.MH rate=float(board["MHS 20s"]), unit=HashUnit.SHA256.MH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[b_id].temp = board["tstemp-2"] hashboards[b_id].temp = board["tstemp-2"]
hashboards[b_id].missing = False hashboards[b_id].missing = False

View File

@@ -172,7 +172,8 @@ class BlackMiner(StockFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
unit=HashUnit.SHA256.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -231,7 +232,7 @@ class BlackMiner(StockFirmware):
hashrate = boards[1].get(f"chain_rate{i}") hashrate = boards[1].get(f"chain_rate{i}")
if hashrate: if hashrate:
hashboard.hashrate = AlgoHashRate.SHA256( hashboard.hashrate = AlgoHashRate.SHA256(
float(hashrate), HashUnit.SHA256.GH rate=float(hashrate), unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
chips = boards[1].get(f"chain_acn{i}") chips = boards[1].get(f"chain_acn{i}")
@@ -275,42 +276,6 @@ class BlackMiner(StockFirmware):
return fans return fans
async def _get_expected_hashrate(
self, rpc_stats: dict = None
) -> Optional[AlgoHashRate]:
# X19 method, not sure compatibility
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_stats is not None:
try:
expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
try:
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
except KeyError:
rate_unit = "GH"
return AlgoHashRate.SHA256(
expected_rate, HashUnit.SHA256.from_str(rate_unit)
).into(self.algo.unit.default)
except LookupError:
pass
async def _get_uptime(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 int(rpc_stats["STATS"][1]["Elapsed"])
except LookupError:
pass
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]: async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
if web_get_system_info is None: if web_get_system_info is None:
try: try:
@@ -357,7 +322,7 @@ class BlackMiner(StockFirmware):
for item in web_summary["SUMMARY"][0]["status"]: for item in web_summary["SUMMARY"][0]["status"]:
try: try:
if not item["status"] == "s": if not item["status"] == "s":
errors.append(X19Error(item["msg"])) errors.append(X19Error(error_message=item["msg"]))
except KeyError: except KeyError:
continue continue
except LookupError: except LookupError:
@@ -400,7 +365,7 @@ class BlackMiner(StockFirmware):
except KeyError: except KeyError:
rate_unit = "GH" rate_unit = "GH"
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
expected_rate, HashUnit.SHA256.from_str(rate_unit) rate=float(expected_rate), unit=HashUnit.SHA256.from_str(rate_unit)
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -87,7 +87,9 @@ class IceRiver(StockFirmware):
if web_userpanel is not None: if web_userpanel is not None:
try: try:
return [Fan(spd) for spd in web_userpanel["userpanel"]["data"]["fans"]] return [
Fan(speed=spd) for spd in web_userpanel["userpanel"]["data"]["fans"]
]
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -130,7 +132,7 @@ class IceRiver(StockFirmware):
try: try:
base_unit = web_userpanel["userpanel"]["data"]["unit"] base_unit = web_userpanel["userpanel"]["data"]["unit"]
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
float( rate=float(
web_userpanel["userpanel"]["data"]["rtpow"].replace( web_userpanel["userpanel"]["data"]["rtpow"].replace(
base_unit, "" base_unit, ""
) )
@@ -186,7 +188,8 @@ class IceRiver(StockFirmware):
hb_list[idx].chip_temp = round(board["outtmp"]) hb_list[idx].chip_temp = round(board["outtmp"])
hb_list[idx].temp = round(board["intmp"]) hb_list[idx].temp = round(board["intmp"])
hb_list[idx].hashrate = AlgoHashRate.SHA256( hb_list[idx].hashrate = AlgoHashRate.SHA256(
float(board["rtpow"].replace("G", "")), HashUnit.SHA256.GH rate=float(board["rtpow"].replace("G", "")),
unit=HashUnit.SHA256.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hb_list[idx].chips = int(board["chipnum"]) hb_list[idx].chips = int(board["chipnum"])
hb_list[idx].missing = False hb_list[idx].missing = False

View File

@@ -187,11 +187,13 @@ class Innosilicon(CGMiner):
try: try:
if "Hash Rate H" in web_get_all["total_hash"].keys(): if "Hash Rate H" in web_get_all["total_hash"].keys():
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
web_get_all["total_hash"]["Hash Rate H"], HashUnit.SHA256.H rate=float(web_get_all["total_hash"]["Hash Rate H"]),
unit=HashUnit.SHA256.H,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
elif "Hash Rate" in web_get_all["total_hash"].keys(): elif "Hash Rate" in web_get_all["total_hash"].keys():
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
web_get_all["total_hash"]["Hash Rate"], HashUnit.SHA256.MH rate=float(web_get_all["total_hash"]["Hash Rate"]),
unit=HashUnit.SHA256.MH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except KeyError: except KeyError:
pass pass
@@ -199,7 +201,8 @@ class Innosilicon(CGMiner):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["MHS 1m"], HashUnit.SHA256.MH rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
unit=HashUnit.SHA256.MH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
@@ -253,7 +256,7 @@ class Innosilicon(CGMiner):
hashrate = board.get("Hash Rate H") hashrate = board.get("Hash Rate H")
if hashrate: if hashrate:
hashboards[idx].hashrate = AlgoHashRate.SHA256( hashboards[idx].hashrate = AlgoHashRate.SHA256(
hashrate, HashUnit.SHA256.H rate=float(hashrate), unit=HashUnit.SHA256.H
).into(self.algo.unit.default) ).into(self.algo.unit.default)
chip_temp = board.get("Temp max") chip_temp = board.get("Temp max")

View File

@@ -179,14 +179,15 @@ class LUXMiner(LuxOSFirmware):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
unit=HashUnit.SHA256.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
hashboards = [ hashboards = [
HashBoard(idx, expected_chips=self.expected_chips) HashBoard(slot=idx, expected_chips=self.expected_chips)
for idx in range(self.expected_hashboards) for idx in range(self.expected_hashboards)
] ]
@@ -202,7 +203,8 @@ class LUXMiner(LuxOSFirmware):
for idx in range(3): for idx in range(3):
board_n = idx + 1 board_n = idx + 1
hashboards[idx].hashrate = AlgoHashRate.SHA256( hashboards[idx].hashrate = AlgoHashRate.SHA256(
float(board_stats[f"chain_rate{board_n}"]), HashUnit.SHA256.GH rate=float(board_stats[f"chain_rate{board_n}"]),
unit=HashUnit.SHA256.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[idx].chips = int(board_stats[f"chain_acn{board_n}"]) hashboards[idx].chips = int(board_stats[f"chain_acn{board_n}"])
chip_temp_data = list( chip_temp_data = list(
@@ -253,7 +255,7 @@ class LUXMiner(LuxOSFirmware):
if rpc_fans is not None: if rpc_fans is not None:
for fan in range(self.expected_fans): for fan in range(self.expected_fans):
try: try:
fans.append(Fan(rpc_fans["FANS"][fan]["RPM"])) fans.append(Fan(speed=rpc_fans["FANS"][fan]["RPM"]))
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
fans.append(Fan()) fans.append(Fan())
return fans return fans
@@ -275,7 +277,7 @@ class LUXMiner(LuxOSFirmware):
except KeyError: except KeyError:
rate_unit = "GH" rate_unit = "GH"
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
expected_rate, HashUnit.SHA256.from_str(rate_unit) rate=float(expected_rate), unit=HashUnit.SHA256.from_str(rate_unit)
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -99,7 +99,7 @@ class MaraMiner(MaraFirmware):
async def set_power_limit(self, wattage: int) -> bool: async def set_power_limit(self, wattage: int) -> bool:
cfg = await self.get_config() cfg = await self.get_config()
cfg.mining_mode = MiningModeConfig.power_tuning(wattage) cfg.mining_mode = MiningModeConfig.power_tuning(power=wattage)
await self.send_config(cfg) await self.send_config(cfg)
return True return True
@@ -179,13 +179,13 @@ class MaraMiner(MaraFirmware):
for hb in web_hashboards["hashboards"]: for hb in web_hashboards["hashboards"]:
idx = hb["index"] idx = hb["index"]
hashboards[idx].hashrate = AlgoHashRate.SHA256( hashboards[idx].hashrate = AlgoHashRate.SHA256(
hb["hashrate_average"], HashUnit.SHA256.GH rate=float(hb["hashrate_average"]), unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
hashboards[idx].temp = round( hashboards[idx].temp = round(
sum(hb["temperature_pcb"]) / len(hb["temperature_pcb"]), 2 sum(hb["temperature_pcb"]) / len(hb["temperature_pcb"])
) )
hashboards[idx].chip_temp = round( hashboards[idx].chip_temp = round(
sum(hb["temperature_chip"]) / len(hb["temperature_chip"]), 2 sum(hb["temperature_chip"]) / len(hb["temperature_chip"])
) )
hashboards[idx].chips = hb["asic_num"] hashboards[idx].chips = hb["asic_num"]
hashboards[idx].serial_number = hb["serial_number"] hashboards[idx].serial_number = hb["serial_number"]
@@ -243,7 +243,7 @@ class MaraMiner(MaraFirmware):
if web_brief is not None: if web_brief is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
web_brief["hashrate_realtime"], HashUnit.SHA256.TH rate=float(web_brief["hashrate_realtime"]), unit=HashUnit.SHA256.TH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass
@@ -259,7 +259,7 @@ class MaraMiner(MaraFirmware):
fans = [] fans = []
for n in range(self.expected_fans): for n in range(self.expected_fans):
try: try:
fans.append(Fan(web_fans["fans"][n]["current_speed"])) fans.append(Fan(speed=web_fans["fans"][n]["current_speed"]))
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
return fans return fans
@@ -291,7 +291,7 @@ class MaraMiner(MaraFirmware):
if web_brief is not None: if web_brief is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
web_brief["hashrate_ideal"], HashUnit.SHA256.GH rate=float(web_brief["hashrate_ideal"]), unit=HashUnit.SHA256.GH
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -208,7 +208,8 @@ class VNish(VNishFirmware, BMMiner):
if rpc_summary is not None: if rpc_summary is not None:
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
unit=HashUnit.SHA256.GH,
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass

View File

@@ -495,6 +495,7 @@ class MinerProtocol(Protocol):
function = getattr(self, getattr(self.data_locations, data_name).cmd) function = getattr(self, getattr(self.data_locations, data_name).cmd)
miner_data[data_name] = await function(**args_to_send) miner_data[data_name] = await function(**args_to_send)
except Exception as e: except Exception as e:
raise e
raise APIError( raise APIError(
f"Failed to call {data_name} on {self} while getting data." f"Failed to call {data_name} on {self} while getting data."
) from e ) from e

View File

@@ -17,6 +17,7 @@ tomli = { version = ">=2.0.1", python = "<3.11" }
tomli-w = "^1.0.0" tomli-w = "^1.0.0"
aiofiles = ">=23.2.1" aiofiles = ">=23.2.1"
betterproto = "2.0.0b7" betterproto = "2.0.0b7"
pydantic = "^2.9.2"
[tool.poetry.group.dev] [tool.poetry.group.dev]
optional = true optional = true