Compare commits

..

22 Commits

Author SHA1 Message Date
Upstream Data
53e1f33fa6 version: bump version number 2025-02-03 09:32:44 -07:00
Upstream Data
16ab2bd4e3 feature: add support for luckyminer LV07 2025-02-03 09:32:16 -07:00
Upstream Data
de728f12d2 version: bump version number 2025-02-03 09:27:43 -07:00
Upstream Data
8092e12dfb feature: add support for S19X88 Hiveon 2025-02-03 09:27:18 -07:00
b-rowan
e70c3e9f79 version: bump version number 2025-01-30 17:56:47 -07:00
b-rowan
39a97f2914 feature: add mac address support for avalon nano 3 2025-01-30 17:56:16 -07:00
b-rowan
35af74ad1a version: bump version number 2025-01-30 07:14:50 -07:00
Erik Olof Gunnar Andersson
15fa91fb98 bug: don’t timeout forever on _socket_ping (#288)
* Don't timeout forever on _socket_ping

* Use factory_get_timeout to configure number of allowed retries

* Add warning log
2025-01-30 07:05:29 -07:00
James Hilliard
084987a3e1 bug: Add cryptography direct dependency (#286) 2025-01-29 10:01:10 -07:00
b-rowan
e93cc77a58 ci: update pre-commit hooks 2025-01-28 21:29:43 -07:00
b-rowan
788d43c51c version: bump version number 2025-01-28 21:27:17 -07:00
Adrian
74c22b82ce bug: add miner algo type for S21 Hydro (#285) 2025-01-28 21:20:39 -07:00
Upstream Data
1f46ce1b9a version: bump version number 2025-01-28 09:36:56 -07:00
Adrian
7964336a0c feature: add support for S21 Hydro (#283) 2025-01-28 09:36:15 -07:00
Upstream Data
a24fc07c2a ci: update pre-commit to run docs generation when committing. 2025-01-28 08:51:13 -07:00
Upstream Data
4c64481d3b bug: do not run unittest when running in precommit.ci 2025-01-28 08:33:02 -07:00
Upstream Data
66fb5834f0 version: bump version number 2025-01-23 13:37:28 -07:00
Upstream Data
db05cc1d97 feature: add support for S21 Pro BOS 2025-01-23 13:37:04 -07:00
Brett Rowan
418e3ce26e Merge pull request #281 from UpstreamData/fix_hammer_pass 2025-01-23 07:42:20 -07:00
John-Paul Compagnone
5a0bb11a44 fix hammer default password 2025-01-23 08:35:37 -05:00
Upstream Data
39f9d087db version: bump version number 2025-01-20 09:27:40 -07:00
Upstream Data
067a376f94 feature: add support for luckyminer LV08 2025-01-20 09:27:16 -07:00
42 changed files with 764 additions and 331 deletions

View File

@@ -1,7 +1,8 @@
ci:
skip:
- poetry-lock
- pytest
- unittest
- generate-docs
repos:
- repo: https://github.com/python-poetry/poetry
rev: 2.0.1
@@ -22,11 +23,11 @@ repos:
exclude: ^mkdocs\.yml$
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 24.10.0
rev: 25.1.0
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 5.13.2
rev: 6.0.0
hooks:
- id: isort
name: isort (python)
@@ -37,6 +38,12 @@ repos:
name: unittest
entry: python -m unittest discover
language: system
'types': [python]
types: [ python ]
args: ["-p '*test.py'"] # Probably this option is absolutely not needed.
pass_filenames: false
- id: generate-docs
name: generate-docs
entry: python docs/generate_miners.py
language: system
types: [ python ]
pass_filenames: false

View File

@@ -2,6 +2,7 @@ import asyncio
import importlib
import os
import warnings
from pathlib import Path
from pyasic.miners.factory import MINER_CLASSES, MinerTypes
@@ -51,12 +52,15 @@ def backend_str(backend: MinerTypes) -> str:
return "Mara Firmware Miners"
case MinerTypes.BITAXE:
return "Stock Firmware BitAxe Miners"
case MinerTypes.LUCKYMINER:
return "Stock Firmware Lucky Miners"
case MinerTypes.ICERIVER:
return "Stock Firmware IceRiver Miners"
case MinerTypes.HAMMER:
return "Stock Firmware Hammer Miners"
case MinerTypes.VOLCMINER:
return "Stock Firmware Volcminers"
raise TypeError("Unknown miner backend, cannot generate docs")
def create_url_str(mtype: str):
@@ -135,14 +139,14 @@ for m in MINER_CLASSES:
done.append(miner)
async def create_directory_structure(directory, data):
def create_directory_structure(directory, data):
if not os.path.exists(directory):
os.makedirs(directory)
for key, value in data.items():
subdirectory = os.path.join(directory, key)
if isinstance(value, dict):
await create_directory_structure(subdirectory, value)
create_directory_structure(subdirectory, value)
elif isinstance(value, list):
file_path = os.path.join(subdirectory + ".md")
@@ -163,7 +167,7 @@ async def create_directory_structure(directory, data):
)
async def create_supported_types(directory):
def create_supported_types(directory):
with open(os.path.join(directory, "supported_types.md"), "w") as file:
file.write(SUPPORTED_TYPES_HEADER)
for mback in MINER_CLASSES:
@@ -180,7 +184,7 @@ async def create_supported_types(directory):
for mtype in backend_types:
file.write(MINER_TYPE_HEADER.format(mtype))
for minstance in backend_types[mtype]:
model = await minstance("1.1.1.1").get_model()
model = minstance("1.1.1.1").model
file.write(
MINER_DETAILS.format(
make(minstance), mtype, create_url_str(model), model
@@ -190,6 +194,7 @@ async def create_supported_types(directory):
file.write(BACKEND_TYPE_CLOSER)
root_directory = os.path.join(os.getcwd(), "miners")
asyncio.run(create_directory_structure(root_directory, m_data))
asyncio.run(create_supported_types(root_directory))
if __name__ == "__main__":
root_directory = Path(__file__).parent.joinpath("miners")
create_directory_structure(root_directory, m_data)
create_supported_types(root_directory)

View File

@@ -495,6 +495,19 @@
show_root_heading: false
heading_level: 0
## S19i (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19i
handler: python
options:
show_root_heading: false
heading_level: 0
## S19j (VNish)
- [x] Shutdowns
@@ -690,6 +703,19 @@
show_root_heading: false
heading_level: 0
## S19 No PIC (Hive)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.hiveon.X19.S19.HiveonS19NoPIC
handler: python
options:
show_root_heading: false
heading_level: 0
## S19 (LuxOS)
- [x] Shutdowns

View File

@@ -14,6 +14,19 @@
show_root_heading: false
heading_level: 0
## S21 Hydro (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Hydro
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 Pro (Stock)
- [x] Shutdowns
@@ -53,6 +66,19 @@
show_root_heading: false
heading_level: 0
## S21 Pro (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21Pro
handler: python
options:
show_root_heading: false
heading_level: 0
## T21 (BOS+)
- [x] Shutdowns

View File

@@ -0,0 +1,29 @@
# pyasic
## LV Models
## LV07 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.luckyminer.espminer.LV.LV07.LuckyMinerLV07
handler: python
options:
show_root_heading: false
heading_level: 0
## LV08 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.luckyminer.espminer.LV.LV08.LuckyMinerLV08
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -105,6 +105,7 @@ details {
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
<li><a href="../antminer/X21#s21-pro-stock">S21 Pro (Stock)</a></li>
<li><a href="../antminer/X21#t21-stock">T21 (Stock)</a></li>
<li><a href="../antminer/X21#s21-hydro-stock">S21 Hydro (Stock)</a></li>
</ul>
</details>
</ul>
@@ -652,6 +653,7 @@ details {
<summary>X21 Series:</summary>
<ul>
<li><a href="../antminer/X21#s21-bos_1">S21 (BOS+)</a></li>
<li><a href="../antminer/X21#s21-pro-bos_1">S21 Pro (BOS+)</a></li>
<li><a href="../antminer/X21#t21-bos_1">T21 (BOS+)</a></li>
</ul>
</details>
@@ -694,6 +696,7 @@ details {
<li><a href="../antminer/X19#s19-no-pic-vnish">S19 No PIC (VNish)</a></li>
<li><a href="../antminer/X19#s19-pro-vnish">S19 Pro (VNish)</a></li>
<li><a href="../antminer/X19#s19j-vnish">S19j (VNish)</a></li>
<li><a href="../antminer/X19#s19i-vnish">S19i (VNish)</a></li>
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
@@ -760,6 +763,7 @@ details {
<ul>
<li><a href="../antminer/X19#s19j-pro-hive">S19j Pro (Hive)</a></li>
<li><a href="../antminer/X19#s19-hive">S19 (Hive)</a></li>
<li><a href="../antminer/X19#s19-no-pic-hive">S19 No PIC (Hive)</a></li>
</ul>
</details>
</ul>
@@ -859,6 +863,18 @@ details {
</ul>
</details>
<details>
<summary>Stock Firmware Lucky Miners:</summary>
<ul>
<details>
<summary>LV Series:</summary>
<ul>
<li><a href="../luckyminer/LV#lv08-stock">LV08 (Stock)</a></li>
<li><a href="../luckyminer/LV#lv07-stock">LV07 (Stock)</a></li>
</ul>
</details>
</ul>
</details>
<details>
<summary>Stock Firmware IceRiver Miners:</summary>
<ul>
<details>

108
poetry.lock generated
View File

@@ -74,18 +74,18 @@ pywin32 = ["pywin32 (>=227)"]
[[package]]
name = "babel"
version = "2.16.0"
version = "2.17.0"
description = "Internationalization utilities"
optional = false
python-versions = ">=3.8"
groups = ["docs"]
files = [
{file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"},
{file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"},
{file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"},
{file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"},
]
[package.extras]
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"]
[[package]]
name = "betterproto"
@@ -110,14 +110,14 @@ rust-codec = ["betterproto-rust-codec (==0.1.1)"]
[[package]]
name = "certifi"
version = "2024.12.14"
version = "2025.1.31"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
groups = ["main", "docs"]
files = [
{file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"},
{file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"},
{file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
{file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
]
[[package]]
@@ -422,19 +422,19 @@ test = ["pytest (>=6)"]
[[package]]
name = "filelock"
version = "3.16.1"
version = "3.17.0"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
{file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
{file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"},
{file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"},
]
[package.extras]
docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"]
typing = ["typing-extensions (>=4.12.2)"]
[[package]]
@@ -457,14 +457,14 @@ dev = ["flake8", "markdown", "twine", "wheel"]
[[package]]
name = "griffe"
version = "1.5.5"
version = "1.5.6"
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
optional = false
python-versions = ">=3.9"
groups = ["docs"]
files = [
{file = "griffe-1.5.5-py3-none-any.whl", hash = "sha256:2761b1e8876c6f1f9ab1af274df93ea6bbadd65090de5f38f4cb5cc84897c7dd"},
{file = "griffe-1.5.5.tar.gz", hash = "sha256:35ee5b38b93d6a839098aad0f92207e6ad6b70c3e8866c08ca669275b8cba585"},
{file = "griffe-1.5.6-py3-none-any.whl", hash = "sha256:b2a3afe497c6c1f952e54a23095ecc09435016293e77af8478ed65df1022a394"},
{file = "griffe-1.5.6.tar.gz", hash = "sha256:181f6666d5aceb6cd6e2da5a2b646cfb431e47a0da1fda283845734b67e10944"},
]
[package.dependencies]
@@ -502,30 +502,30 @@ files = [
[[package]]
name = "h2"
version = "4.1.0"
description = "HTTP/2 State-Machine based protocol implementation"
version = "4.2.0"
description = "Pure-Python HTTP/2 protocol implementation"
optional = false
python-versions = ">=3.6.1"
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"},
{file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"},
{file = "h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0"},
{file = "h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f"},
]
[package.dependencies]
hpack = ">=4.0,<5"
hyperframe = ">=6.0,<7"
hpack = ">=4.1,<5"
hyperframe = ">=6.1,<7"
[[package]]
name = "hpack"
version = "4.0.0"
description = "Pure-Python HPACK header compression"
version = "4.1.0"
description = "Pure-Python HPACK header encoding"
optional = false
python-versions = ">=3.6.1"
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"},
{file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"},
{file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"},
{file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"},
]
[[package]]
@@ -577,26 +577,26 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "hyperframe"
version = "6.0.1"
description = "HTTP/2 framing layer for Python"
version = "6.1.0"
description = "Pure-Python HTTP/2 framing"
optional = false
python-versions = ">=3.6.1"
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"},
{file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"},
{file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"},
{file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"},
]
[[package]]
name = "identify"
version = "2.6.5"
version = "2.6.6"
description = "File identification library for Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "identify-2.6.5-py2.py3-none-any.whl", hash = "sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566"},
{file = "identify-2.6.5.tar.gz", hash = "sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc"},
{file = "identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881"},
{file = "identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251"},
]
[package.extras]
@@ -619,15 +619,15 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2
[[package]]
name = "importlib-metadata"
version = "8.5.0"
version = "8.6.1"
description = "Read metadata from Python packages"
optional = false
python-versions = ">=3.8"
python-versions = ">=3.9"
groups = ["docs"]
markers = "python_version < \"3.10\""
files = [
{file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"},
{file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"},
{file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"},
{file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"},
]
[package.dependencies]
@@ -639,7 +639,7 @@ cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
enabler = ["pytest-enabler (>=2.2)"]
perf = ["ipython"]
test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
type = ["pytest-mypy"]
[[package]]
@@ -846,14 +846,14 @@ pyyaml = ">=5.1"
[[package]]
name = "mkdocs-material"
version = "9.5.50"
version = "9.6.2"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
groups = ["docs"]
files = [
{file = "mkdocs_material-9.5.50-py3-none-any.whl", hash = "sha256:f24100f234741f4d423a9d672a909d859668a4f404796be3cf035f10d6050385"},
{file = "mkdocs_material-9.5.50.tar.gz", hash = "sha256:ae5fe16f3d7c9ccd05bb6916a7da7420cf99a9ce5e33debd9d40403a090d5825"},
{file = "mkdocs_material-9.6.2-py3-none-any.whl", hash = "sha256:71d90dbd63b393ad11a4d90151dfe3dcbfcd802c0f29ce80bebd9bbac6abc753"},
{file = "mkdocs_material-9.6.2.tar.gz", hash = "sha256:a3de1c5d4c745f10afa78b1a02f917b9dce0808fb206adc0f5bb48b58c1ca21f"},
]
[package.dependencies]
@@ -1127,14 +1127,14 @@ type = ["mypy (>=1.11.2)"]
[[package]]
name = "pre-commit"
version = "4.0.1"
version = "4.1.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"},
{file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"},
{file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"},
{file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"},
]
[package.dependencies]
@@ -1177,14 +1177,14 @@ files = [
[[package]]
name = "pydantic"
version = "2.10.5"
version = "2.10.6"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"},
{file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"},
{file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
{file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
]
[package.dependencies]
@@ -1326,14 +1326,14 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pymdown-extensions"
version = "10.14"
version = "10.14.3"
description = "Extension pack for Python Markdown."
optional = false
python-versions = ">=3.8"
groups = ["docs"]
files = [
{file = "pymdown_extensions-10.14-py3-none-any.whl", hash = "sha256:202481f716cc8250e4be8fce997781ebf7917701b59652458ee47f2401f818b5"},
{file = "pymdown_extensions-10.14.tar.gz", hash = "sha256:741bd7c4ff961ba40b7528d32284c53bc436b8b1645e8e37c3e57770b8700a34"},
{file = "pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9"},
{file = "pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b"},
]
[package.dependencies]
@@ -1760,4 +1760,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.1"
python-versions = ">3.9, <4.0"
content-hash = "6f5bcdfff0891ee60759be4a2ccb175049706e174b041fd2b33b1e85afd1edba"
content-hash = "67a8e0d34c0d1af0f8e4d617d80fd5afc8d197e5d5444f78fd82e4f716a52965"

View File

@@ -143,12 +143,12 @@ class MinerConfig(BaseModel):
**self.pools.as_mara(user_suffix=user_suffix),
}
def as_bitaxe(self, user_suffix: str | None = None) -> dict:
def as_espminer(self, user_suffix: str | None = None) -> dict:
return {
**self.fan_mode.as_bitaxe(),
**self.temperature.as_bitaxe(),
**self.mining_mode.as_bitaxe(),
**self.pools.as_bitaxe(user_suffix=user_suffix),
**self.fan_mode.as_espminer(),
**self.temperature.as_espminer(),
**self.mining_mode.as_espminer(),
**self.pools.as_espminer(user_suffix=user_suffix),
}
def as_luxos(self, user_suffix: str | None = None) -> dict:
@@ -272,10 +272,10 @@ class MinerConfig(BaseModel):
)
@classmethod
def from_bitaxe(cls, web_system_info: dict) -> "MinerConfig":
def from_espminer(cls, web_system_info: dict) -> "MinerConfig":
return cls(
pools=PoolConfig.from_bitaxe(web_system_info),
fan_mode=FanModeConfig.from_bitaxe(web_system_info),
pools=PoolConfig.from_espminer(web_system_info),
fan_mode=FanModeConfig.from_espminer(web_system_info),
)
@classmethod

View File

@@ -61,8 +61,8 @@ class MinerConfigOption(Enum):
def as_mara(self) -> dict:
return self.value.as_mara()
def as_bitaxe(self) -> dict:
return self.value.as_bitaxe()
def as_espminer(self) -> dict:
return self.value.as_espminer()
def as_luxos(self) -> dict:
return self.value.as_luxos()
@@ -125,7 +125,7 @@ class MinerConfigValue(BaseModel):
def as_mara(self) -> dict:
return {}
def as_bitaxe(self) -> dict:
def as_espminer(self) -> dict:
return {}
def as_luxos(self) -> dict:

View File

@@ -81,7 +81,7 @@ class FanModeNormal(MinerConfigValue):
},
}
def as_bitaxe(self) -> dict:
def as_espminer(self) -> dict:
return {"autoFanspeed": 1}
def as_luxos(self) -> dict:
@@ -156,7 +156,7 @@ class FanModeManual(MinerConfigValue):
},
}
def as_bitaxe(self) -> dict:
def as_espminer(self) -> dict:
return {"autoFanspeed": 0, "fanspeed": self.speed}
def as_luxos(self) -> dict:
@@ -342,7 +342,7 @@ class FanModeConfig(MinerConfigOption):
return cls.default()
@classmethod
def from_bitaxe(cls, web_system_info: dict):
def from_espminer(cls, web_system_info: dict):
if web_system_info["autofanspeed"] == 1:
return cls.normal()
else:

View File

@@ -102,7 +102,7 @@ class Pool(MinerConfigValue):
"pass": self.password,
}
def as_bitaxe(self, user_suffix: str | None = None) -> dict:
def as_espminer(self, user_suffix: str | None = None) -> dict:
return {
"stratumURL": self.url,
"stratumUser": f"{self.user}{user_suffix or ''}",
@@ -192,7 +192,7 @@ class Pool(MinerConfigValue):
)
@classmethod
def from_bitaxe(cls, web_system_info: dict) -> "Pool":
def from_espminer(cls, web_system_info: dict) -> "Pool":
url = f"stratum+tcp://{web_system_info['stratumURL']}:{web_system_info['stratumPort']}"
return cls(
url=url,
@@ -306,8 +306,8 @@ class PoolGroup(MinerConfigValue):
def as_mara(self, user_suffix: str | None = None) -> list:
return [p.as_mara(user_suffix=user_suffix) for p in self.pools]
def as_bitaxe(self, user_suffix: str | None = None) -> dict:
return self.pools[0].as_bitaxe(user_suffix=user_suffix)
def as_espminer(self, user_suffix: str | None = None) -> dict:
return self.pools[0].as_espminer(user_suffix=user_suffix)
def as_boser(self, user_suffix: str | None = None) -> PoolGroupConfiguration:
return PoolGroupConfiguration(
@@ -395,8 +395,8 @@ class PoolGroup(MinerConfigValue):
return cls(pools=[Pool.from_mara(pool_conf) for pool_conf in web_config_pools])
@classmethod
def from_bitaxe(cls, web_system_info: dict) -> "PoolGroup":
return cls(pools=[Pool.from_bitaxe(web_system_info)])
def from_espminer(cls, web_system_info: dict) -> "PoolGroup":
return cls(pools=[Pool.from_espminer(web_system_info)])
@classmethod
def from_iceriver(cls, web_userpanel: dict) -> "PoolGroup":
@@ -507,8 +507,8 @@ class PoolConfig(MinerConfigValue):
return {"pools": self.groups[0].as_mara(user_suffix=user_suffix)}
return {"pools": []}
def as_bitaxe(self, user_suffix: str | None = None) -> dict:
return self.groups[0].as_bitaxe(user_suffix=user_suffix)
def as_espminer(self, user_suffix: str | None = None) -> dict:
return self.groups[0].as_espminer(user_suffix=user_suffix)
def as_luxos(self, user_suffix: str | None = None) -> dict:
return {}
@@ -576,8 +576,8 @@ class PoolConfig(MinerConfigValue):
return cls(groups=[PoolGroup.from_mara(web_config["pools"])])
@classmethod
def from_bitaxe(cls, web_system_info: dict) -> "PoolConfig":
return cls(groups=[PoolGroup.from_bitaxe(web_system_info)])
def from_espminer(cls, web_system_info: dict) -> "PoolConfig":
return cls(groups=[PoolGroup.from_espminer(web_system_info)])
@classmethod
def from_iceriver(cls, web_userpanel: dict) -> "PoolConfig":

View File

@@ -26,6 +26,7 @@ class MinerMake(str, Enum):
AURADINE = "Auradine"
EPIC = "ePIC"
BITAXE = "BitAxe"
LUCKYMINER = "LuckyMiner"
ICERIVER = "IceRiver"
HAMMER = "Hammer"
VOLCMINER = "VolcMiner"

View File

@@ -59,6 +59,7 @@ class AntminerModels(MinerModelType):
T19 = "T19"
S21 = "S21"
S21Pro = "S21 Pro"
S21Hydro = "S21 Hydro"
T21 = "T21"
def __str__(self):
@@ -507,6 +508,14 @@ class BitAxeModels(MinerModelType):
return self.value
class LuckyMinerModels(MinerModelType):
LV07 = "LV07"
LV08 = "LV08"
def __str__(self):
return self.value
class IceRiverModels(MinerModelType):
KS0 = "KS0"
KS1 = "KS1"
@@ -550,6 +559,7 @@ class MinerModel:
AURADINE = AuradineModels
EPIC = ePICModels
BITAXE = BitAxeModels
LUCKYMINER = LuckyMinerModels
ICERIVER = IceRiverModels
HAMMER = HammerModels
VOLCMINER = VolcMinerModels

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerModern
from pyasic.miners.device.models import S21, S21Pro
from pyasic.miners.device.models import S21, S21Hydro, S21Pro
class BMMinerS21(AntminerModern, S21):
@@ -24,3 +24,7 @@ class BMMinerS21(AntminerModern, S21):
class BMMinerS21Pro(AntminerModern, S21Pro):
pass
class BMMinerS21Hydro(AntminerModern, S21Hydro):
pass

View File

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

View File

@@ -15,8 +15,12 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSer
from pyasic.miners.device.models import S21
from pyasic.miners.device.models import S21, S21Pro
class BOSMinerS21(BOSer, S21):
pass
class BOSMinerS21Pro(BOSer, S21Pro):
pass

View File

@@ -14,5 +14,5 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .S21 import BOSMinerS21
from .S21 import BOSMinerS21, BOSMinerS21Pro
from .T21 import BOSMinerT21

View File

@@ -27,6 +27,7 @@ from pyasic.miners.device.models import (
S19jNoPIC,
S19jPro,
S19KPro,
S19NoPIC,
S19Plus,
S19Pro,
S19ProHydro,
@@ -39,6 +40,10 @@ class HiveonS19(HiveonModern, S19):
pass
class HiveonS19NoPIC(HiveonModern, S19NoPIC):
pass
class HiveonS19Plus(HiveonModern, S19Plus):
pass

View File

@@ -25,6 +25,7 @@ from .S19 import (
HiveonS19jPro,
HiveonS19KPro,
HiveonS19L,
HiveonS19NoPIC,
HiveonS19Plus,
HiveonS19Pro,
HiveonS19ProHydro,

View File

@@ -13,10 +13,91 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from typing import Optional
from pyasic import APIError
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.data import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.miners.device.models import AvalonNano3
from pyasic.web.avalonminer import AvalonMinerWebAPI
AVALON_NANO_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"_get_mac",
[WebAPICommand("web_minerinfo", "get_minerinfo")],
),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver",
[RPCAPICommand("rpc_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver",
[RPCAPICommand("rpc_version", "version")],
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("rpc_devs", "devs")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"_get_env_temp",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_pools", "pools")],
),
}
)
class CGMinerAvalonNano3(AvalonMiner, AvalonNano3):
pass
_web_cls = AvalonMinerWebAPI
web: AvalonMinerWebAPI
data_locations = AVALON_NANO_DATA_LOC
async def _get_mac(self, web_minerinfo: dict) -> Optional[dict]:
if web_minerinfo is None:
try:
web_minerinfo = await self.web.minerinfo()
except APIError:
pass
if web_minerinfo is not None:
try:
mac = web_minerinfo.get("mac")
if mac is not None:
return mac.upper()
except (KeyError, ValueError):
pass

View File

@@ -28,6 +28,7 @@ from .hammer import BlackMiner
from .hiveon import HiveonModern, HiveonOld
from .iceriver import IceRiver
from .innosilicon import Innosilicon
from .luckyminer import LuckyMiner
from .luxminer import LUXMiner
from .marathon import MaraMiner
from .unknown import UnknownMiner

View File

@@ -1,235 +1,7 @@
from typing import List, Optional
from pyasic import APIError, MinerConfig
from pyasic.data import Fan, HashBoard
from pyasic.device.algorithm import AlgoHashRate
from pyasic.device.firmware import MinerFirmware
from pyasic.miners.base import BaseMiner
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
from pyasic.web.bitaxe import BitAxeWebAPI
BITAXE_DATA_LOC = DataLocations(
**{
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.MAC): DataFunction(
"_get_mac",
[WebAPICommand("web_system_info", "system/info")],
),
}
)
from pyasic.miners.backends.espminer import ESPMiner
class BitAxe(BaseMiner):
class BitAxe(ESPMiner):
"""Handler for BitAxe"""
web: BitAxeWebAPI
_web_cls = BitAxeWebAPI
firmware = MinerFirmware.STOCK
data_locations = BITAXE_DATA_LOC
async def reboot(self) -> bool:
await self.web.restart()
return True
async def get_config(self) -> MinerConfig:
web_system_info = await self.web.system_info()
return MinerConfig.from_bitaxe(web_system_info)
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
await self.web.update_settings(**config.as_bitaxe())
async def _get_wattage(self, web_system_info: dict = None) -> Optional[int]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return round(web_system_info["power"])
except KeyError:
pass
async def _get_hashrate(
self, web_system_info: dict = None
) -> Optional[AlgoHashRate]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return self.algo.hashrate(
rate=float(web_system_info["hashRate"]), unit=self.algo.unit.GH
).into(self.algo.unit.default)
except KeyError:
pass
async def _get_expected_hashrate(
self, web_system_info: dict = None
) -> Optional[AlgoHashRate]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
expected_hashrate = (
web_system_info.get("smallCoreCount")
* web_system_info.get("asicCount")
* web_system_info.get("frequency")
)
return self.algo.hashrate(
rate=float(expected_hashrate), unit=self.algo.unit.MH
).into(self.algo.unit.default)
except KeyError:
pass
async def _get_uptime(self, web_system_info: dict = None) -> Optional[int]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return web_system_info["uptimeSeconds"]
except KeyError:
pass
async def _get_hashboards(self, web_system_info: dict = None) -> List[HashBoard]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return [
HashBoard(
hashrate=self.algo.hashrate(
rate=float(web_system_info["hashRate"]),
unit=self.algo.unit.GH,
).into(self.algo.unit.default),
chip_temp=web_system_info.get("temp"),
temp=web_system_info.get("vrTemp"),
chips=web_system_info.get("asicCount", 1),
expected_chips=self.expected_chips,
missing=False,
active=True,
voltage=web_system_info.get("voltage"),
)
]
except KeyError:
pass
return []
async def _get_fans(self, web_system_info: dict = None) -> List[Fan]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return [Fan(speed=web_system_info["fanrpm"])]
except KeyError:
pass
return []
async def _get_hostname(self, web_system_info: dict = None) -> Optional[str]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return web_system_info["hostname"]
except KeyError:
pass
async def _get_api_ver(self, web_system_info: dict = None) -> Optional[str]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return web_system_info["version"]
except KeyError:
pass
async def _get_fw_ver(self, web_system_info: dict = None) -> Optional[str]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return web_system_info["version"]
except KeyError:
pass
async def _get_mac(self, web_system_info: dict = None) -> Optional[str]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return web_system_info["macAddr"].upper()
except KeyError:
pass
pass

View File

@@ -0,0 +1,235 @@
from typing import List, Optional
from pyasic import APIError, MinerConfig
from pyasic.data import Fan, HashBoard
from pyasic.device.algorithm import AlgoHashRate
from pyasic.device.firmware import MinerFirmware
from pyasic.miners.base import BaseMiner
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
from pyasic.web.espminer import ESPMinerWebAPI
ESPMINER_DATA_LOC = DataLocations(
**{
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.MAC): DataFunction(
"_get_mac",
[WebAPICommand("web_system_info", "system/info")],
),
}
)
class ESPMiner(BaseMiner):
"""Handler for ESPMiner"""
web: ESPMinerWebAPI
_web_cls = ESPMinerWebAPI
firmware = MinerFirmware.STOCK
data_locations = ESPMINER_DATA_LOC
async def reboot(self) -> bool:
await self.web.restart()
return True
async def get_config(self) -> MinerConfig:
web_system_info = await self.web.system_info()
return MinerConfig.from_espminer(web_system_info)
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
await self.web.update_settings(**config.as_espminer())
async def _get_wattage(self, web_system_info: dict = None) -> Optional[int]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return round(web_system_info["power"])
except KeyError:
pass
async def _get_hashrate(
self, web_system_info: dict = None
) -> Optional[AlgoHashRate]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return self.algo.hashrate(
rate=float(web_system_info["hashRate"]), unit=self.algo.unit.GH
).into(self.algo.unit.default)
except KeyError:
pass
async def _get_expected_hashrate(
self, web_system_info: dict = None
) -> Optional[AlgoHashRate]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
expected_hashrate = (
web_system_info.get("smallCoreCount")
* web_system_info.get("asicCount")
* web_system_info.get("frequency")
)
return self.algo.hashrate(
rate=float(expected_hashrate), unit=self.algo.unit.MH
).into(self.algo.unit.default)
except KeyError:
pass
async def _get_uptime(self, web_system_info: dict = None) -> Optional[int]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return web_system_info["uptimeSeconds"]
except KeyError:
pass
async def _get_hashboards(self, web_system_info: dict = None) -> List[HashBoard]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return [
HashBoard(
hashrate=self.algo.hashrate(
rate=float(web_system_info["hashRate"]),
unit=self.algo.unit.GH,
).into(self.algo.unit.default),
chip_temp=web_system_info.get("temp"),
temp=web_system_info.get("vrTemp"),
chips=web_system_info.get("asicCount", 1),
expected_chips=self.expected_chips,
missing=False,
active=True,
voltage=web_system_info.get("voltage"),
)
]
except KeyError:
pass
return []
async def _get_fans(self, web_system_info: dict = None) -> List[Fan]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return [Fan(speed=web_system_info["fanrpm"])]
except KeyError:
pass
return []
async def _get_hostname(self, web_system_info: dict = None) -> Optional[str]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return web_system_info["hostname"]
except KeyError:
pass
async def _get_api_ver(self, web_system_info: dict = None) -> Optional[str]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return web_system_info["version"]
except KeyError:
pass
async def _get_fw_ver(self, web_system_info: dict = None) -> Optional[str]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return web_system_info["version"]
except KeyError:
pass
async def _get_mac(self, web_system_info: dict = None) -> Optional[str]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return web_system_info["macAddr"].upper()
except KeyError:
pass

View File

@@ -0,0 +1,7 @@
from pyasic.miners.backends.espminer import ESPMiner
class LuckyMiner(ESPMiner):
"""Handler for LuckyMiner"""
pass

View File

@@ -50,6 +50,10 @@ class BitAxeMake(BaseMiner):
make = MinerMake.BITAXE
class LuckyMinerMake(BaseMiner):
make = MinerMake.LUCKYMINER
class IceRiverMake(BaseMiner):
make = MinerMake.ICERIVER

View File

@@ -34,3 +34,12 @@ class S21Pro(AntMinerMake):
expected_fans = 4
expected_hashboards = 3
algo = MinerAlgo.SHA256
class S21Hydro(AntMinerMake):
raw_model = MinerModel.ANTMINER.S21Hydro
expected_chips = 216
expected_hashboards = 2
expected_fans = 0
algo = MinerAlgo.SHA256

View File

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

View File

@@ -0,0 +1,12 @@
from pyasic.device.algorithm import MinerAlgo
from pyasic.device.models import MinerModel
from pyasic.miners.device.makes import LuckyMinerMake
class LV07(LuckyMinerMake):
raw_model = MinerModel.LUCKYMINER.LV07
expected_hashboards = 1
expected_chips = 1
expected_fans = 1
algo = MinerAlgo.SHA256

View File

@@ -0,0 +1,12 @@
from pyasic.device.algorithm import MinerAlgo
from pyasic.device.models import MinerModel
from pyasic.miners.device.makes import LuckyMinerMake
class LV08(LuckyMinerMake):
raw_model = MinerModel.LUCKYMINER.LV08
expected_hashboards = 1
expected_chips = 1
expected_fans = 1
algo = MinerAlgo.SHA256

View File

@@ -0,0 +1,2 @@
from .LV07 import LV07
from .LV08 import LV08

View File

@@ -0,0 +1 @@
from .LV import *

View File

@@ -41,6 +41,7 @@ from pyasic.miners.goldshell import *
from pyasic.miners.hammer import *
from pyasic.miners.iceriver import *
from pyasic.miners.innosilicon import *
from pyasic.miners.luckyminer import *
from pyasic.miners.volcminer import *
from pyasic.miners.whatsminer import *
@@ -62,6 +63,7 @@ class MinerTypes(enum.Enum):
ICERIVER = 13
HAMMER = 14
VOLCMINER = 15
LUCKYMINER = 16
MINER_CLASSES = {
@@ -117,6 +119,7 @@ MINER_CLASSES = {
"ANTMINER BHB68606": BMMinerS21, # ???
"ANTMINER S21 PRO": BMMinerS21Pro,
"ANTMINER T21": BMMinerT21,
"ANTMINER S21 HYD.": BMMinerS21Hydro,
},
MinerTypes.WHATSMINER: {
None: type("WhatsminerUnknown", (BTMiner, WhatsMinerMake), {}),
@@ -544,6 +547,7 @@ MINER_CLASSES = {
"ANTMINER S19 PRO+ HYD.": BOSMinerS19ProPlusHydro,
"ANTMINER T19": BOSMinerT19,
"ANTMINER S21": BOSMinerS21,
"ANTMINER S21 PRO": BOSMinerS21Pro,
"ANTMINER T21": BOSMinerT21,
"BRAIINS MINI MINER BMM 100": BraiinsBMM100,
"BRAIINS MINI MINER BMM 101": BraiinsBMM101,
@@ -559,7 +563,7 @@ MINER_CLASSES = {
"ANTMINER S19NOPIC": VNishS19NoPIC,
"ANTMINER S19 PRO": VNishS19Pro,
"ANTMINER S19J": VNishS19j,
"ANTMINER S19I": VNishS19j,
"ANTMINER S19I": VNishS19i,
"ANTMINER S19J PRO": VNishS19jPro,
"ANTMINER S19J PRO A": VNishS19jPro,
"ANTMINER S19J PRO BB": VNishS19jPro,
@@ -592,6 +596,7 @@ MINER_CLASSES = {
"ANTMINER T9": HiveonT9,
"ANTMINER S19JPRO": HiveonS19jPro,
"ANTMINER S19": HiveonS19,
"ANTMINER S19X88": HiveonS19NoPIC,
},
MinerTypes.LUX_OS: {
None: LUXMiner,
@@ -634,6 +639,11 @@ MINER_CLASSES = {
"BM1397": BitAxeMax,
"BM1370": BitAxeGamma,
},
MinerTypes.LUCKYMINER: {
None: LuckyMiner,
"LV08": LuckyMinerLV08,
"LV07": LuckyMinerLV07,
},
MinerTypes.ICERIVER: {
None: type("IceRiverUnknown", (IceRiver, IceRiverMake), {}),
"KS0": IceRiverKS0,
@@ -731,6 +741,7 @@ class MinerFactory:
MinerTypes.AURADINE: self.get_miner_model_auradine,
MinerTypes.MARATHON: self.get_miner_model_marathon,
MinerTypes.BITAXE: self.get_miner_model_bitaxe,
MinerTypes.LUCKYMINER: self.get_miner_model_luckyminer,
MinerTypes.ICERIVER: self.get_miner_model_iceriver,
MinerTypes.HAMMER: self.get_miner_model_hammer,
MinerTypes.VOLCMINER: self.get_miner_model_volcminer,
@@ -833,6 +844,8 @@ class MinerFactory:
return MinerTypes.ICERIVER
if "AxeOS" in web_text:
return MinerTypes.BITAXE
if "Lucky miner" in web_text:
return MinerTypes.LUCKYMINER
if "cloud-box" in web_text:
return MinerTypes.GOLDSHELL
if "AnthillOS" in web_text:
@@ -877,6 +890,7 @@ class MinerFactory:
await writer.drain()
# loop to receive all the data
timeouts_remaining = max(1, int(settings.get("factory_get_timeout", 3)))
while True:
try:
d = await asyncio.wait_for(reader.read(4096), timeout=1)
@@ -884,7 +898,10 @@ class MinerFactory:
break
data += d
except asyncio.TimeoutError:
pass
timeouts_remaining -= 1
if not timeouts_remaining:
logger.warning(f"{ip}: Socket ping timeout.")
break
except ConnectionResetError:
return
except asyncio.CancelledError:
@@ -1292,6 +1309,18 @@ class MinerFactory:
except (TypeError, LookupError):
pass
async def get_miner_model_luckyminer(self, ip: str) -> str | None:
web_json_data = await self.send_web_command(ip, "/api/system/info")
try:
miner_model = web_json_data["minerModel"]
if miner_model == "":
return None
return miner_model
except (TypeError, LookupError):
pass
async def get_miner_model_iceriver(self, ip: str) -> str | None:
async with httpx.AsyncClient(transport=settings.transport()) as client:
try:

View File

@@ -0,0 +1 @@
from .espminer import *

View File

@@ -0,0 +1,6 @@
from pyasic.miners.backends.luckyminer import LuckyMiner
from pyasic.miners.device.models.luckyminer import LV07
class LuckyMinerLV07(LuckyMiner, LV07):
pass

View File

@@ -0,0 +1,6 @@
from pyasic.miners.backends.luckyminer import LuckyMiner
from pyasic.miners.device.models.luckyminer import LV08
class LuckyMinerLV08(LuckyMiner, LV08):
pass

View File

@@ -0,0 +1,2 @@
from .LV07 import LuckyMinerLV07
from .LV08 import LuckyMinerLV08

View File

@@ -0,0 +1 @@
from .LV import *

View File

@@ -31,7 +31,7 @@ _settings = { # defaults
"default_whatsminer_rpc_password": "admin",
"default_innosilicon_web_password": "admin",
"default_antminer_web_password": "root",
"default_hammer_web_password": "ltc@dog",
"default_hammer_web_password": "root",
"default_volcminer_web_password": "ltc@dog",
"default_bosminer_web_password": "root",
"default_vnish_web_password": "admin",

110
pyasic/web/avalonminer.py Normal file
View File

@@ -0,0 +1,110 @@
# ------------------------------------------------------------------------------
# Copyright 2022 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from __future__ import annotations
import asyncio
import hashlib
import json
from pathlib import Path
from typing import Any
import aiofiles
import httpx
from pyasic import settings
from pyasic.web.base import BaseWebAPI
class AvalonMinerWebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None:
"""Initialize the modern Avalonminer API client with a specific IP address.
Args:
ip (str): IP address of the Avalonminer device.
"""
super().__init__(ip)
self.username = "root"
self.pwd = settings.get("default_avalonminer_web_password", "root")
async def send_command(
self,
command: str | bytes,
ignore_errors: bool = False,
allow_warning: bool = True,
**parameters: Any,
) -> dict:
"""Send a command to the Avalonminer device using HTTP digest authentication.
Args:
command (str | bytes): The CGI command to send.
ignore_errors (bool): If True, ignore any HTTP errors.
allow_warning (bool): If True, proceed with warnings.
**parameters: Arbitrary keyword arguments to be sent as parameters in the request.
Returns:
dict: The JSON response from the device or an empty dictionary if an error occurs.
"""
cookie_data = "ff0000ff" + hashlib.sha256(self.pwd.encode()).hexdigest()[:24]
url = f"http://{self.ip}:{self.port}/{command}.cgi"
try:
async with httpx.AsyncClient(transport=settings.transport()) as client:
client.cookies.set("auth", cookie_data)
resp = await client.get(url)
raw_data = resp.text.replace("minerinfoCallback(", "").replace(");", "")
return json.loads(raw_data)
except httpx.HTTPError:
pass
return {}
async def multicommand(
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
) -> dict:
async with httpx.AsyncClient(transport=settings.transport()) as client:
cookie_data = (
"ff0000ff" + hashlib.sha256(self.pwd.encode()).hexdigest()[:24]
)
client.cookies.set("auth", cookie_data)
tasks = [
asyncio.create_task(self._handle_multicommand(client, command))
for command in commands
]
all_data = await asyncio.gather(*tasks)
data = {}
for item in all_data:
data.update(item)
data["multicommand"] = True
return data
async def _handle_multicommand(
self, client: httpx.AsyncClient, command: str
) -> dict:
try:
url = f"http://{self.ip}:{self.port}/{command}.cgi"
resp = await client.get(url)
raw_data = resp.text.replace("minerinfoCallback(", "").replace(");", "")
return json.loads(raw_data)
except httpx.HTTPError:
pass
return {}
async def minerinfo(self):
return await self.send_command("get_minerinfo")
async def home(self):
return await self.send_command("get_home")

View File

@@ -10,7 +10,7 @@ from pyasic import APIError, settings
from pyasic.web.base import BaseWebAPI
class BitAxeWebAPI(BaseWebAPI):
class ESPMinerWebAPI(BaseWebAPI):
async def send_command(
self,
command: str | bytes,

7
pyasic/web/luckyminer.py Normal file
View File

@@ -0,0 +1,7 @@
from __future__ import annotations
from .bitaxe import ESPMinerWebAPI
class LuckyMinerWebAPI(ESPMinerWebAPI):
pass

View File

@@ -1,6 +1,6 @@
[project]
name = "pyasic"
version = "0.70.1"
version = "0.71.2"
description = "A simplified and standardized interface for Bitcoin ASICs."
authors = [{name = "UpstreamData", email = "brett@upstreamdata.ca"}]
@@ -44,6 +44,7 @@ requires-python = ">3.9, <4.0"
dependencies = [
"httpx>=0.26.0",
"asyncssh>=2.17.0",
"cryptography>=39.0",
"passlib>=1.7.4",
"pyaml>=23.12.0",
"tomli (>=2.2.1,<3.0.0) ; python_version < '3.11'",