Compare commits

...

57 Commits

Author SHA1 Message Date
Upstream Data
261527a380 feature: add support for hammer D10 and hammer discovery 2024-11-14 08:45:59 -07:00
Upstream Data
924b62e0d5 feature: add support for hammer miner 2024-11-14 08:45:59 -07:00
Temi
76a870c2ed feat: Add _get_pools method for Bmminer. (#233)
* backends: Add _get_pools for Bmminer

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

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

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-11-13 10:18:07 -07:00
Upstream Data
309356243b version: bump version number 2024-11-12 14:28:51 -07:00
Upstream Data
e9b4cc9bd6 feature: add Antminer D9 support 2024-11-12 14:28:31 -07:00
Upstream Data
648c54de93 bug: add iceriver KS5 chip count 2024-11-12 14:25:13 -07:00
Upstream Data
e1ce96ab1b bug: add innosilicon A11 chip count 2024-11-12 14:24:05 -07:00
Brett Rowan
86860a8dc4 version: bump version number 2024-11-08 09:18:34 -07:00
Jacob Roy
5212641f45 add json serialization to the Scheme class (#232) 2024-11-06 15:13:30 -07:00
Upstream Data
52432e6043 version: bump version number 2024-11-06 09:06:11 -07:00
Upstream Data
727e484860 docs: update docs 2024-11-06 09:05:08 -07:00
Upstream Data
6c091756d2 feature: add support for Iceriver KS5 and submodels 2024-11-06 09:04:38 -07:00
Upstream Data
14533ce4fe version: bump version number 2024-11-05 09:46:35 -07:00
Upstream Data
82d1840039 bug: fix inf and nan in factory 2024-11-05 09:46:11 -07:00
Brett Rowan
8e6240cdba feature: LuxOS fixes and updates (#192)
* feature: add luxos tuner support to config.

* feature: add luxos temp control support to config.

* bug: fix failure to identify luxOS miners sometimes.

* feature: add get_config with temperature parsing.

* bug: fix some handling in hashboards.

* feature: add API version and fw version.

* refactor: improve RPC api handling.

* refactor: remove useless code, and tag bug.

* feature: add fault light check support.

* refactor: improve fanset compatibility.

* feature: add fan config parsing.

* feature: add pools parsing from luxos.

---------

Co-authored-by: Upstream Data <brett@upstreamdata.ca>
2024-11-05 09:43:34 -07:00
pre-commit-ci[bot]
5749e173d1 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v5.0.0)
- [github.com/psf/black: 24.3.0 → 24.10.0](https://github.com/psf/black/compare/24.3.0...24.10.0)
2024-11-05 09:38:40 -07:00
Upstream Data
7d682b62ac version: bump version number 2024-11-05 09:37:18 -07:00
Upstream Data
6739a1001f docs: update supported types 2024-11-05 09:36:58 -07:00
Upstream Data
56e4a5307f feature: add support for Antminer K7 2024-11-05 09:36:27 -07:00
Upstream Data
88de27c9e7 feature: add support for Whatsminer M50VH90 2024-11-05 09:32:30 -07:00
Upstream Data
a77113c4db feature: add support for Innosilicon A11 2024-11-05 09:27:15 -07:00
Upstream Data
c19945bb82 feature: add support for Vnish S19j Pro BB 2024-11-05 09:22:11 -07:00
Upstream Data
1756937d20 feature: add support for Antminer Z15 Pro 2024-11-05 09:18:04 -07:00
Upstream Data
c7b7fe864b feature: improve vnish is_mining functionality
Fixes #225
2024-11-05 09:12:13 -07:00
John-Paul Compagnone
e7ebefd1bf style fix 2024-11-04 09:13:57 -07:00
John-Paul Compagnone
4677efbc46 fix typo for marathon backend 2024-11-04 09:13:57 -07:00
Brett Rowan
4b7a1a0495 bug: fix lockfile failing release 2024-11-01 16:05:59 -06:00
Brett Rowan
cc4e7da4e5 version: bump version number 2024-11-01 16:02:35 -06:00
Brett Rowan
a3d2d7d35e feature: add web parsing for luxOS type 2024-11-01 16:02:15 -06:00
Brett Rowan
d67de98bd0 version: bump version number 2024-11-01 10:52:04 -06:00
Brett Rowan
fd1a3e459b bug: reset betterproto version so that it can be built for pypi 2024-11-01 10:51:49 -06:00
Brett Rowan
adcab694b5 version: bump version number. 2024-11-01 10:46:59 -06:00
Brett Rowan
2bb097272f fix: use git version of better proto for compatibility with home assistant 2024-11-01 10:46:45 -06:00
Upstream Data
896968dded version: bump version number 2024-10-30 14:48:41 -06:00
Upstream Data
56b8f7c5b3 feature: parse iceriver config 2024-10-30 14:48:15 -06:00
Upstream Data
0ed7559aef version: bump version number 2024-10-30 14:37:47 -06:00
Upstream Data
275d87e4fe bug: fix AntminerOld board parsing 2024-10-30 14:37:19 -06:00
Upstream Data
c3ab814d77 version: bump version number 2024-10-30 14:08:38 -06:00
Upstream Data
05a8569205 bug: fix some calcuation errors with pool info 2024-10-30 14:08:22 -06:00
Upstream Data
b098cb8136 version: bump version number 2024-10-30 13:40:44 -06:00
Upstream Data
75fe7857e4 feature: add iceriver pool metrics 2024-10-30 13:39:22 -06:00
Upstream Data
66797aced1 ci: remove deprecated stages arg from pre-commit 2024-10-30 13:20:41 -06:00
Upstream Data
4a71e38078 ci: fix test framework name 2024-10-30 13:19:46 -06:00
Upstream Data
9fb07e4fa3 ci: skip pre-commit CI running pytest 2024-10-30 13:19:12 -06:00
Upstream Data
74792771ec version: bump version number 2024-10-30 13:16:22 -06:00
Upstream Data
fa6e8a976d bug: fix iceriver userpanel parsing 2024-10-30 13:16:07 -06:00
James Hilliard
f20531cff5 Run pre-commit formatting on all files 2024-10-30 13:07:57 -06:00
Upstream Data
8b1cbed9ce version: bump version number. 2024-10-30 12:25:47 -06:00
Upstream Data
0194e13427 bug: update A11MX chip count 2024-10-30 12:25:32 -06:00
Upstream Data
82d71abf54 version: bump version number 2024-10-30 12:23:08 -06:00
Upstream Data
e71cfadf6e bug: add model parser into map for iceriver 2024-10-30 12:22:44 -06:00
Upstream Data
18931c4e98 version: bump version number 2024-10-30 12:18:53 -06:00
Upstream Data
8622c080aa bug: fix inno pool parsing 2024-10-30 12:18:27 -06:00
Upstream Data
cb71b2a593 docs: update supported miners 2024-10-30 10:44:37 -06:00
Upstream Data
ff5956da41 version: bump version number 2024-10-30 10:32:48 -06:00
Upstream Data
acdafc2efd bug: fix hashboard innosilicon model selection 2024-10-30 10:32:29 -06:00
Upstream Data
b8874092ad bug: fix hashboard count on A11MX 2024-10-30 10:07:59 -06:00
90 changed files with 1935 additions and 346 deletions

View File

@@ -1,12 +1,15 @@
ci:
skip:
- unittest
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0 rev: v5.0.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: check-yaml - id: check-yaml
- id: check-added-large-files - id: check-added-large-files
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 24.3.0 rev: 24.10.0
hooks: hooks:
- id: black - id: black
- repo: https://github.com/pycqa/isort - repo: https://github.com/pycqa/isort
@@ -24,4 +27,3 @@ repos:
'types': [python] 'types': [python]
args: ["-p '*test.py'"] # Probably this option is absolutely not needed. args: ["-p '*test.py'"] # Probably this option is absolutely not needed.
pass_filenames: false pass_filenames: false
stages: [commit]

View File

@@ -8,3 +8,10 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## Z15 Pro (Stock)
::: pyasic.miners.antminer.bmminer.X15.Z15.BMMinerZ15Pro
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -274,6 +274,13 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19j Pro (VNish)
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19jPro
handler: python
options:
show_root_heading: false
heading_level: 4
## S19a (VNish) ## S19a (VNish)
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19a ::: pyasic.miners.antminer.vnish.X19.S19.VNishS19a
handler: python handler: python

View File

@@ -29,6 +29,13 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## T21 (BOS+)
::: pyasic.miners.antminer.bosminer.X21.T21.BOSMinerT21
handler: python
options:
show_root_heading: false
heading_level: 4
## S21 (VNish) ## S21 (VNish)
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21 ::: pyasic.miners.antminer.vnish.X21.S21.VNishS21
handler: python handler: python

View File

@@ -22,6 +22,20 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## KA3 (Stock)
::: pyasic.miners.antminer.bmminer.X3.KA3.BMMinerKA3
handler: python
options:
show_root_heading: false
heading_level: 4
## KS3 (Stock)
::: pyasic.miners.antminer.bmminer.X3.KS3.BMMinerKS3
handler: python
options:
show_root_heading: false
heading_level: 4
## L3+ (VNish) ## L3+ (VNish)
::: pyasic.miners.antminer.vnish.X3.L3.VnishL3Plus ::: pyasic.miners.antminer.vnish.X3.L3.VnishL3Plus
handler: python handler: python

View File

@@ -8,3 +8,10 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## KS5 (Stock)
::: pyasic.miners.antminer.bmminer.X5.KS5.BMMinerKS5
handler: python
options:
show_root_heading: false
heading_level: 4

View File

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

View File

@@ -0,0 +1,10 @@
# pyasic
## nano Models
## Avalon Nano 3 (Stock)
::: pyasic.miners.avalonminer.cgminer.nano.nano3.CGMinerAvalonNano3
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -1,6 +1,20 @@
# pyasic # pyasic
## KSX Models ## KSX Models
## KS0 (Stock)
::: pyasic.miners.iceriver.iceminer.KSX.KS0.IceRiverKS0
handler: python
options:
show_root_heading: false
heading_level: 4
## KS1 (Stock)
::: pyasic.miners.iceriver.iceminer.KSX.KS1.IceRiverKS1
handler: python
options:
show_root_heading: false
heading_level: 4
## KS2 (Stock) ## KS2 (Stock)
::: pyasic.miners.iceriver.iceminer.KSX.KS2.IceRiverKS2 ::: pyasic.miners.iceriver.iceminer.KSX.KS2.IceRiverKS2
handler: python handler: python
@@ -8,3 +22,45 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## KS3 (Stock)
::: pyasic.miners.iceriver.iceminer.KSX.KS3.IceRiverKS3
handler: python
options:
show_root_heading: false
heading_level: 4
## KS3L (Stock)
::: pyasic.miners.iceriver.iceminer.KSX.KS3.IceRiverKS3L
handler: python
options:
show_root_heading: false
heading_level: 4
## KS3M (Stock)
::: pyasic.miners.iceriver.iceminer.KSX.KS3.IceRiverKS3M
handler: python
options:
show_root_heading: false
heading_level: 4
## KS5 (Stock)
::: pyasic.miners.iceriver.iceminer.KSX.KS5.IceRiverKS5
handler: python
options:
show_root_heading: false
heading_level: 4
## KS5L (Stock)
::: pyasic.miners.iceriver.iceminer.KSX.KS5.IceRiverKS5L
handler: python
options:
show_root_heading: false
heading_level: 4
## KS5M (Stock)
::: pyasic.miners.iceriver.iceminer.KSX.KS5.IceRiverKS5M
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,17 @@
# pyasic
## A11X Models
## A11 (Stock)
::: pyasic.miners.innosilicon.cgminer.A11X.A11.InnosiliconA11
handler: python
options:
show_root_heading: false
heading_level: 4
## A11MX (Stock)
::: pyasic.miners.innosilicon.cgminer.A11X.A11M.InnosiliconA11MX
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -21,18 +21,22 @@ details {
<li><a href="../antminer/X3#d3-stock">D3 (Stock)</a></li> <li><a href="../antminer/X3#d3-stock">D3 (Stock)</a></li>
<li><a href="../antminer/X3#hs3-stock">HS3 (Stock)</a></li> <li><a href="../antminer/X3#hs3-stock">HS3 (Stock)</a></li>
<li><a href="../antminer/X3#l3_1-stock">L3+ (Stock)</a></li> <li><a href="../antminer/X3#l3_1-stock">L3+ (Stock)</a></li>
<li><a href="../antminer/X3#ka3-stock">KA3 (Stock)</a></li>
<li><a href="../antminer/X3#ks3-stock">KS3 (Stock)</a></li>
</ul> </ul>
</details> </details>
<details> <details>
<summary>X5 Series:</summary> <summary>X5 Series:</summary>
<ul> <ul>
<li><a href="../antminer/X5#dr5-stock">DR5 (Stock)</a></li> <li><a href="../antminer/X5#dr5-stock">DR5 (Stock)</a></li>
<li><a href="../antminer/X5#ks5-stock">KS5 (Stock)</a></li>
</ul> </ul>
</details> </details>
<details> <details>
<summary>X7 Series:</summary> <summary>X7 Series:</summary>
<ul> <ul>
<li><a href="../antminer/X7#l7-stock">L7 (Stock)</a></li> <li><a href="../antminer/X7#l7-stock">L7 (Stock)</a></li>
<li><a href="../antminer/X7#k7-stock">K7 (Stock)</a></li>
</ul> </ul>
</details> </details>
<details> <details>
@@ -49,6 +53,7 @@ details {
<summary>X15 Series:</summary> <summary>X15 Series:</summary>
<ul> <ul>
<li><a href="../antminer/X15#z15-stock">Z15 (Stock)</a></li> <li><a href="../antminer/X15#z15-stock">Z15 (Stock)</a></li>
<li><a href="../antminer/X15#z15-pro-stock">Z15 Pro (Stock)</a></li>
</ul> </ul>
</details> </details>
<details> <details>
@@ -278,6 +283,7 @@ details {
<li><a href="../whatsminer/M5X#m50-vh60-stock">M50 VH60 (Stock)</a></li> <li><a href="../whatsminer/M5X#m50-vh60-stock">M50 VH60 (Stock)</a></li>
<li><a href="../whatsminer/M5X#m50-vh70-stock">M50 VH70 (Stock)</a></li> <li><a href="../whatsminer/M5X#m50-vh70-stock">M50 VH70 (Stock)</a></li>
<li><a href="../whatsminer/M5X#m50-vh80-stock">M50 VH80 (Stock)</a></li> <li><a href="../whatsminer/M5X#m50-vh80-stock">M50 VH80 (Stock)</a></li>
<li><a href="../whatsminer/M5X#m50-vh90-stock">M50 VH90 (Stock)</a></li>
<li><a href="../whatsminer/M5X#m50-vj10-stock">M50 VJ10 (Stock)</a></li> <li><a href="../whatsminer/M5X#m50-vj10-stock">M50 VJ10 (Stock)</a></li>
<li><a href="../whatsminer/M5X#m50-vj20-stock">M50 VJ20 (Stock)</a></li> <li><a href="../whatsminer/M5X#m50-vj20-stock">M50 VJ20 (Stock)</a></li>
<li><a href="../whatsminer/M5X#m50-vj30-stock">M50 VJ30 (Stock)</a></li> <li><a href="../whatsminer/M5X#m50-vj30-stock">M50 VJ30 (Stock)</a></li>
@@ -378,6 +384,12 @@ details {
<li><a href="../avalonminer/A12X#avalon-1246-stock">Avalon 1246 (Stock)</a></li> <li><a href="../avalonminer/A12X#avalon-1246-stock">Avalon 1246 (Stock)</a></li>
</ul> </ul>
</details> </details>
<details>
<summary>nano Series:</summary>
<ul>
<li><a href="../avalonminer/nano#avalon-nano-3-stock">Avalon Nano 3 (Stock)</a></li>
</ul>
</details>
</ul> </ul>
</details> </details>
<details> <details>
@@ -395,6 +407,13 @@ details {
<li><a href="../innosilicon/A10X#a10x-stock">A10X (Stock)</a></li> <li><a href="../innosilicon/A10X#a10x-stock">A10X (Stock)</a></li>
</ul> </ul>
</details> </details>
<details>
<summary>A11X Series:</summary>
<ul>
<li><a href="../innosilicon/A11X#a11-stock">A11 (Stock)</a></li>
<li><a href="../innosilicon/A11X#a11mx-stock">A11MX (Stock)</a></li>
</ul>
</details>
</ul> </ul>
</details> </details>
<details> <details>
@@ -470,6 +489,7 @@ details {
<summary>X21 Series:</summary> <summary>X21 Series:</summary>
<ul> <ul>
<li><a href="../antminer/X21#s21-bos_1">S21 (BOS+)</a></li> <li><a href="../antminer/X21#s21-bos_1">S21 (BOS+)</a></li>
<li><a href="../antminer/X21#t21-bos_1">T21 (BOS+)</a></li>
</ul> </ul>
</details> </details>
</ul> </ul>
@@ -505,6 +525,7 @@ details {
<li><a href="../antminer/X19#s19-pro-vnish">S19 Pro (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#s19j-vnish">S19j (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>
<li><a href="../antminer/X19#s19a-vnish">S19a (VNish)</a></li> <li><a href="../antminer/X19#s19a-vnish">S19a (VNish)</a></li>
<li><a href="../antminer/X19#s19a-pro-vnish">S19a Pro (VNish)</a></li> <li><a href="../antminer/X19#s19a-pro-vnish">S19a Pro (VNish)</a></li>
<li><a href="../antminer/X19#s19-pro-hydro-vnish">S19 Pro Hydro (VNish)</a></li> <li><a href="../antminer/X19#s19-pro-hydro-vnish">S19 Pro Hydro (VNish)</a></li>
@@ -661,7 +682,15 @@ details {
<details> <details>
<summary>KSX Series:</summary> <summary>KSX Series:</summary>
<ul> <ul>
<li><a href="../iceriver/KSX#ks0-stock">KS0 (Stock)</a></li>
<li><a href="../iceriver/KSX#ks1-stock">KS1 (Stock)</a></li>
<li><a href="../iceriver/KSX#ks2-stock">KS2 (Stock)</a></li> <li><a href="../iceriver/KSX#ks2-stock">KS2 (Stock)</a></li>
<li><a href="../iceriver/KSX#ks3-stock">KS3 (Stock)</a></li>
<li><a href="../iceriver/KSX#ks3l-stock">KS3L (Stock)</a></li>
<li><a href="../iceriver/KSX#ks3m-stock">KS3M (Stock)</a></li>
<li><a href="../iceriver/KSX#ks5-stock">KS5 (Stock)</a></li>
<li><a href="../iceriver/KSX#ks5l-stock">KS5L (Stock)</a></li>
<li><a href="../iceriver/KSX#ks5m-stock">KS5M (Stock)</a></li>
</ul> </ul>
</details> </details>
</ul> </ul>

View File

@@ -71,6 +71,13 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50 VH90 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH90
handler: python
options:
show_root_heading: false
heading_level: 4
## M50 VJ10 (Stock) ## M50 VJ10 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ10 ::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ10
handler: python handler: python

64
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]] [[package]]
name = "aiofiles" name = "aiofiles"
@@ -1121,41 +1121,41 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[[package]] [[package]]
name = "watchdog" name = "watchdog"
version = "5.0.3" version = "6.0.0"
description = "Filesystem events monitoring" description = "Filesystem events monitoring"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
files = [ files = [
{file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:85527b882f3facda0579bce9d743ff7f10c3e1e0db0a0d0e28170a7d0e5ce2ea"}, {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"},
{file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53adf73dcdc0ef04f7735066b4a57a4cd3e49ef135daae41d77395f0b5b692cb"}, {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"},
{file = "watchdog-5.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e25adddab85f674acac303cf1f5835951345a56c5f7f582987d266679979c75b"}, {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"},
{file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f01f4a3565a387080dc49bdd1fefe4ecc77f894991b88ef927edbfa45eb10818"}, {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"},
{file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91b522adc25614cdeaf91f7897800b82c13b4b8ac68a42ca959f992f6990c490"}, {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"},
{file = "watchdog-5.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d52db5beb5e476e6853da2e2d24dbbbed6797b449c8bf7ea118a4ee0d2c9040e"}, {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"},
{file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d11b07c64f63f49876e0ab8042ae034674c8653bfcdaa8c4b32e71cfff87e8"}, {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"},
{file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:349c9488e1d85d0a58e8cb14222d2c51cbc801ce11ac3936ab4c3af986536926"}, {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"},
{file = "watchdog-5.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:53a3f10b62c2d569e260f96e8d966463dec1a50fa4f1b22aec69e3f91025060e"}, {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"},
{file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:950f531ec6e03696a2414b6308f5c6ff9dab7821a768c9d5788b1314e9a46ca7"}, {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"},
{file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6deb336cba5d71476caa029ceb6e88047fc1dc74b62b7c4012639c0b563906"}, {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"},
{file = "watchdog-5.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1021223c08ba8d2d38d71ec1704496471ffd7be42cfb26b87cd5059323a389a1"}, {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"},
{file = "watchdog-5.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:752fb40efc7cc8d88ebc332b8f4bcbe2b5cc7e881bccfeb8e25054c00c994ee3"}, {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"},
{file = "watchdog-5.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a2e8f3f955d68471fa37b0e3add18500790d129cc7efe89971b8a4cc6fdeb0b2"}, {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"},
{file = "watchdog-5.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b8ca4d854adcf480bdfd80f46fdd6fb49f91dd020ae11c89b3a79e19454ec627"}, {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"},
{file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:90a67d7857adb1d985aca232cc9905dd5bc4803ed85cfcdcfcf707e52049eda7"}, {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"},
{file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:720ef9d3a4f9ca575a780af283c8fd3a0674b307651c1976714745090da5a9e8"}, {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"},
{file = "watchdog-5.0.3-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:223160bb359281bb8e31c8f1068bf71a6b16a8ad3d9524ca6f523ac666bb6a1e"}, {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"},
{file = "watchdog-5.0.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:560135542c91eaa74247a2e8430cf83c4342b29e8ad4f520ae14f0c8a19cfb5b"}, {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"},
{file = "watchdog-5.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dd021efa85970bd4824acacbb922066159d0f9e546389a4743d56919b6758b91"}, {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"},
{file = "watchdog-5.0.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:78864cc8f23dbee55be34cc1494632a7ba30263951b5b2e8fc8286b95845f82c"}, {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"},
{file = "watchdog-5.0.3-py3-none-manylinux2014_i686.whl", hash = "sha256:1e9679245e3ea6498494b3028b90c7b25dbb2abe65c7d07423ecfc2d6218ff7c"}, {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"},
{file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:9413384f26b5d050b6978e6fcd0c1e7f0539be7a4f1a885061473c5deaa57221"}, {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"},
{file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:294b7a598974b8e2c6123d19ef15de9abcd282b0fbbdbc4d23dfa812959a9e05"}, {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"},
{file = "watchdog-5.0.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:26dd201857d702bdf9d78c273cafcab5871dd29343748524695cecffa44a8d97"}, {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"},
{file = "watchdog-5.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:0f9332243355643d567697c3e3fa07330a1d1abf981611654a1f2bf2175612b7"}, {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"},
{file = "watchdog-5.0.3-py3-none-win32.whl", hash = "sha256:c66f80ee5b602a9c7ab66e3c9f36026590a0902db3aea414d59a2f55188c1f49"}, {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"},
{file = "watchdog-5.0.3-py3-none-win_amd64.whl", hash = "sha256:f00b4cf737f568be9665563347a910f8bdc76f88c2970121c86243c8cfdf90e9"}, {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"},
{file = "watchdog-5.0.3-py3-none-win_ia64.whl", hash = "sha256:49f4d36cb315c25ea0d946e018c01bb028048023b9e103d3d3943f58e109dd45"}, {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"},
{file = "watchdog-5.0.3.tar.gz", hash = "sha256:108f42a7f0345042a854d4d0ad0834b741d421330d5f575b81cb27b883500176"}, {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"},
] ]
[package.extras] [package.extras]

View File

@@ -148,6 +148,17 @@ class MinerConfig:
**self.pools.as_bitaxe(user_suffix=user_suffix), **self.pools.as_bitaxe(user_suffix=user_suffix),
} }
def as_luxos(self, user_suffix: str = None) -> dict:
return {
**self.fan_mode.as_luxos(),
**self.temperature.as_luxos(),
**self.mining_mode.as_luxos(),
**self.pools.as_luxos(user_suffix=user_suffix),
}
def as_hammer(self, *args, **kwargs) -> dict:
return self.as_am_modern(*args, **kwargs)
@classmethod @classmethod
def from_dict(cls, dict_conf: dict) -> "MinerConfig": def from_dict(cls, dict_conf: dict) -> "MinerConfig":
"""Constructs a MinerConfig object from a dictionary.""" """Constructs a MinerConfig object from a dictionary."""
@@ -250,3 +261,25 @@ class MinerConfig:
pools=PoolConfig.from_bitaxe(web_system_info), pools=PoolConfig.from_bitaxe(web_system_info),
fan_mode=FanModeConfig.from_bitaxe(web_system_info), fan_mode=FanModeConfig.from_bitaxe(web_system_info),
) )
@classmethod
def from_iceriver(cls, web_userpanel: dict) -> "MinerConfig":
return cls(
pools=PoolConfig.from_iceriver(web_userpanel),
)
@classmethod
def from_luxos(
cls, rpc_tempctrl: dict, rpc_fans: dict, rpc_pools: dict, rpc_groups: dict
) -> "MinerConfig":
return cls(
temperature=TemperatureConfig.from_luxos(rpc_tempctrl=rpc_tempctrl),
fan_mode=FanModeConfig.from_luxos(
rpc_tempctrl=rpc_tempctrl, rpc_fans=rpc_fans
),
pools=PoolConfig.from_luxos(rpc_pools=rpc_pools, rpc_groups=rpc_groups),
)
@classmethod
def from_hammer(cls, *args, **kwargs) -> "MinerConfig":
return cls.from_am_modern(*args, **kwargs)

View File

@@ -63,6 +63,9 @@ class MinerConfigOption(Enum):
def as_bitaxe(self) -> dict: def as_bitaxe(self) -> dict:
return self.value.as_bitaxe() return self.value.as_bitaxe()
def as_luxos(self) -> dict:
return self.value.as_luxos()
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
return self.value(*args, **kwargs) return self.value(*args, **kwargs)
@@ -125,6 +128,9 @@ class MinerConfigValue:
def as_bitaxe(self) -> dict: def as_bitaxe(self) -> dict:
return {} return {}
def as_luxos(self) -> dict:
return {}
def __getitem__(self, item): def __getitem__(self, item):
try: try:
return getattr(self, item) return getattr(self, item)

View File

@@ -83,6 +83,9 @@ class FanModeNormal(MinerConfigValue):
def as_bitaxe(self) -> dict: def as_bitaxe(self) -> dict:
return {"autoFanspeed": 1} return {"autoFanspeed": 1}
def as_luxos(self) -> dict:
return {"fanset": {"speed": -1, "min_fans": self.minimum_fans}}
@dataclass @dataclass
class FanModeManual(MinerConfigValue): class FanModeManual(MinerConfigValue):
@@ -144,6 +147,9 @@ class FanModeManual(MinerConfigValue):
def as_bitaxe(self) -> dict: def as_bitaxe(self) -> dict:
return {"autoFanspeed": 0, "fanspeed": self.speed} return {"autoFanspeed": 0, "fanspeed": self.speed}
def as_luxos(self) -> dict:
return {"fanset": {"speed": self.speed, "min_fans": self.minimum_fans}}
@dataclass @dataclass
class FanModeImmersion(MinerConfigValue): class FanModeImmersion(MinerConfigValue):
@@ -167,6 +173,9 @@ class FanModeImmersion(MinerConfigValue):
def as_mara(self) -> dict: def as_mara(self) -> dict:
return {"general-config": {"environment-profile": "OilImmersionCooling"}} return {"general-config": {"environment-profile": "OilImmersionCooling"}}
def as_luxos(self) -> dict:
return {"fanset": {"speed": 0, "min_fans": 0}}
class FanModeConfig(MinerConfigOption): class FanModeConfig(MinerConfigOption):
normal = FanModeNormal normal = FanModeNormal
@@ -304,3 +313,23 @@ class FanModeConfig(MinerConfigOption):
return cls.normal() return cls.normal()
else: else:
return cls.manual(speed=web_system_info["fanspeed"]) return cls.manual(speed=web_system_info["fanspeed"])
@classmethod
def from_luxos(cls, rpc_fans: dict, rpc_tempctrl: dict):
try:
mode = rpc_tempctrl["TEMPCTRL"][0]["Mode"]
if mode == "Manual":
speed = rpc_fans["FANS"][0]["Speed"]
min_fans = rpc_fans["FANCTRL"][0]["MinFans"]
if min_fans == 0 and speed == 0:
return cls.immersion()
return cls.manual(
speed=speed,
minimum_fans=min_fans,
)
return cls.normal(
minimum_fans=rpc_fans["FANCTRL"][0]["MinFans"],
)
except LookupError:
pass
return cls.default()

View File

@@ -70,6 +70,9 @@ class MiningModeNormal(MinerConfigValue):
} }
} }
def as_luxos(self) -> dict:
return {"autotunerset": {"enabled": False}}
@dataclass @dataclass
class MiningModeSleep(MinerConfigValue): class MiningModeSleep(MinerConfigValue):
@@ -240,6 +243,9 @@ class MiningModePowerTune(MinerConfigValue):
} }
} }
def as_luxos(self) -> dict:
return {"autotunerset": {"enabled": True}}
@dataclass @dataclass
class MiningModeHashrateTune(MinerConfigValue): class MiningModeHashrateTune(MinerConfigValue):
@@ -333,6 +339,9 @@ class MiningModeHashrateTune(MinerConfigValue):
} }
} }
def as_luxos(self) -> dict:
return {"autotunerset": {"enabled": True}}
@dataclass @dataclass
class ManualBoardSettings(MinerConfigValue): class ManualBoardSettings(MinerConfigValue):

View File

@@ -222,6 +222,18 @@ class Pool(MinerConfigValue):
password=web_system_info.get("stratumPassword", ""), password=web_system_info.get("stratumPassword", ""),
) )
@classmethod
def from_luxos(cls, rpc_pools: dict) -> "Pool":
return cls.from_api(rpc_pools)
@classmethod
def from_iceriver(cls, web_pool: dict) -> "Pool":
return cls(
url=web_pool["addr"],
user=web_pool["user"],
password=web_pool["pass"],
)
@dataclass @dataclass
class PoolGroup(MinerConfigValue): class PoolGroup(MinerConfigValue):
@@ -402,6 +414,15 @@ class PoolGroup(MinerConfigValue):
def from_bitaxe(cls, web_system_info: dict) -> "PoolGroup": def from_bitaxe(cls, web_system_info: dict) -> "PoolGroup":
return cls(pools=[Pool.from_bitaxe(web_system_info)]) return cls(pools=[Pool.from_bitaxe(web_system_info)])
@classmethod
def from_iceriver(cls, web_userpanel: dict) -> "PoolGroup":
return cls(
pools=[
Pool.from_iceriver(web_pool)
for web_pool in web_userpanel["data"]["pools"]
]
)
@dataclass @dataclass
class PoolConfig(MinerConfigValue): class PoolConfig(MinerConfigValue):
@@ -506,6 +527,9 @@ class PoolConfig(MinerConfigValue):
def as_bitaxe(self, user_suffix: str = None) -> dict: def as_bitaxe(self, user_suffix: str = None) -> dict:
return self.groups[0].as_bitaxe(user_suffix=user_suffix) return self.groups[0].as_bitaxe(user_suffix=user_suffix)
def as_luxos(self, user_suffix: str = None) -> dict:
return {}
@classmethod @classmethod
def from_api(cls, api_pools: dict) -> "PoolConfig": def from_api(cls, api_pools: dict) -> "PoolConfig":
try: try:
@@ -568,3 +592,24 @@ class PoolConfig(MinerConfigValue):
@classmethod @classmethod
def from_bitaxe(cls, web_system_info: dict) -> "PoolConfig": def from_bitaxe(cls, web_system_info: dict) -> "PoolConfig":
return cls(groups=[PoolGroup.from_bitaxe(web_system_info)]) return cls(groups=[PoolGroup.from_bitaxe(web_system_info)])
@classmethod
def from_iceriver(cls, web_userpanel: dict) -> "PoolConfig":
return cls(groups=[PoolGroup.from_iceriver(web_userpanel)])
@classmethod
def from_luxos(cls, rpc_groups: dict, rpc_pools: dict) -> "PoolConfig":
return cls(
groups=[
PoolGroup(
pools=[
Pool.from_luxos(pool)
for pool in rpc_pools["POOLS"]
if pool["GROUP"] == group["GROUP"]
],
name=group["Name"],
quota=group["Quota"],
)
for group in rpc_groups["GROUPS"]
]
)

View File

@@ -54,6 +54,9 @@ class TemperatureConfig(MinerConfigValue):
temps_config["temps"]["shutdown"] = self.hot temps_config["temps"]["shutdown"] = self.hot
return temps_config return temps_config
def as_luxos(self) -> dict:
return {"tempctrlset": [self.target or "", self.hot or "", self.danger or ""]}
@classmethod @classmethod
def from_dict(cls, dict_conf: dict | None) -> "TemperatureConfig": def from_dict(cls, dict_conf: dict | None) -> "TemperatureConfig":
return cls( return cls(
@@ -130,3 +133,16 @@ class TemperatureConfig(MinerConfigValue):
return cls(**conf) return cls(**conf)
return cls.default() return cls.default()
@classmethod
def from_luxos(cls, rpc_tempctrl: dict) -> "TemperatureConfig":
try:
tempctrl_config = rpc_tempctrl["TEMPCTRL"][0]
return cls(
target=tempctrl_config.get("Target"),
hot=tempctrl_config.get("Hot"),
danger=tempctrl_config.get("Dangerous"),
)
except LookupError:
pass
return cls.default()

View File

@@ -23,13 +23,13 @@ from typing import Any, List, Union
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 .boards import HashBoard from .boards import HashBoard
from .device import DeviceInfo from .device import DeviceInfo
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
from .fans import Fan from .fans import Fan
from .hashrate import AlgoHashRate, HashUnit from .hashrate import AlgoHashRate, HashUnit
from pyasic.data.pools import PoolMetrics
@dataclass @dataclass
@@ -154,7 +154,11 @@ class MinerData:
@staticmethod @staticmethod
def dict_factory(x): def dict_factory(x):
return {k: v for (k, v) in x if not k.startswith("_")} 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._datetime = datetime.now(timezone.utc).astimezone()

View File

@@ -89,6 +89,8 @@ class PoolMetrics:
@staticmethod @staticmethod
def _calculate_percentage(value: int, total: int) -> float: def _calculate_percentage(value: int, total: int) -> float:
"""Calculate the percentage.""" """Calculate the percentage."""
if value is None or total is None:
return 0
if total == 0: if total == 0:
return 0 return 0
return (value / total) * 100 return (value / total) * 100

View File

@@ -27,6 +27,7 @@ class MinerMake(str, Enum):
EPIC = "ePIC" EPIC = "ePIC"
BITAXE = "BitAxe" BITAXE = "BitAxe"
ICERIVER = "IceRiver" ICERIVER = "IceRiver"
HAMMER = "Hammer"
def __str__(self): def __str__(self):
return self.value return self.value

View File

@@ -10,12 +10,15 @@ class AntminerModels(str, Enum):
DR5 = "DR5" DR5 = "DR5"
KS5 = "KS5" KS5 = "KS5"
L7 = "L7" L7 = "L7"
K7 = "K7"
E9Pro = "E9Pro" E9Pro = "E9Pro"
S9 = "S9" S9 = "S9"
S9i = "S9i" S9i = "S9i"
S9j = "S9j" S9j = "S9j"
T9 = "T9" T9 = "T9"
D9 = "D9"
Z15 = "Z15" Z15 = "Z15"
Z15Pro = "Z15 Pro"
S17 = "S17" S17 = "S17"
S17Plus = "S17+" S17Plus = "S17+"
S17Pro = "S17 Pro" S17Pro = "S17 Pro"
@@ -223,6 +226,7 @@ class WhatsminerModels(str, Enum):
M50VH60 = "M50 VH60" M50VH60 = "M50 VH60"
M50VH70 = "M50 VH70" M50VH70 = "M50 VH70"
M50VH80 = "M50 VH80" M50VH80 = "M50 VH80"
M50VH90 = "M50 VH90"
M50VJ10 = "M50 VJ10" M50VJ10 = "M50 VJ10"
M50VJ20 = "M50 VJ20" M50VJ20 = "M50 VJ20"
M50VJ30 = "M50 VJ30" M50VJ30 = "M50 VJ30"
@@ -296,6 +300,7 @@ class AvalonminerModels(str, Enum):
class InnosiliconModels(str, Enum): class InnosiliconModels(str, Enum):
T3HPlus = "T3H+" T3HPlus = "T3H+"
A10X = "A10X" A10X = "A10X"
A11 = "A11"
A11MX = "A11MX" A11MX = "A11MX"
def __str__(self): def __str__(self):
@@ -351,6 +356,16 @@ class IceRiverModels(str, Enum):
KS3 = "KS3" KS3 = "KS3"
KS3L = "KS3L" KS3L = "KS3L"
KS3M = "KS3M" KS3M = "KS3M"
KS5 = "KS5"
KS5L = "KS5L"
KS5M = "KS5M"
def __str__(self):
return self.value
class HammerModels(str, Enum):
D10 = "D10"
def __str__(self): def __str__(self):
return self.value return self.value
@@ -366,3 +381,4 @@ class MinerModel:
EPIC = ePICModels EPIC = ePICModels
BITAXE = BitAxeModels BITAXE = BitAxeModels
ICERIVER = IceRiverModels ICERIVER = IceRiverModels
HAMMER = HammerModels

View File

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

View File

@@ -0,0 +1,16 @@
# ------------------------------------------------------------------------------
# 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 .Z15 import BMMinerZ15Pro

View File

@@ -23,6 +23,7 @@ from .S19 import (
BMMinerS19j, BMMinerS19j,
BMMinerS19jNoPIC, BMMinerS19jNoPIC,
BMMinerS19jPro, BMMinerS19jPro,
BMMinerS19KPro,
BMMinerS19L, BMMinerS19L,
BMMinerS19Plus, BMMinerS19Plus,
BMMinerS19Pro, BMMinerS19Pro,
@@ -30,6 +31,5 @@ from .S19 import (
BMMinerS19ProPlus, BMMinerS19ProPlus,
BMMinerS19ProPlusHydro, BMMinerS19ProPlusHydro,
BMMinerS19XP, BMMinerS19XP,
BMMinerS19KPro,
) )
from .T19 import BMMinerT19 from .T19 import BMMinerT19

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .D9 import BMMinerD9
from .E9 import BMMinerE9Pro from .E9 import BMMinerE9Pro
from .S9 import BMMinerS9, BMMinerS9i, BMMinerS9j from .S9 import BMMinerS9, BMMinerS9i, BMMinerS9j
from .T9 import BMMinerT9 from .T9 import BMMinerT9

View File

@@ -17,6 +17,7 @@ from .X3 import *
from .X5 import * from .X5 import *
from .X7 import * from .X7 import *
from .X9 import * from .X9 import *
from .X15 import *
from .X17 import * from .X17 import *
from .X19 import * from .X19 import *
from .X21 import * from .X21 import *

View File

@@ -15,7 +15,4 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .S21 import ePICS21, ePICS21Pro from .S21 import ePICS21, ePICS21Pro
from .T21 import ePICT21
from .T21 import (
ePICT21,
)

View File

@@ -17,16 +17,19 @@ from .antminer import AntminerModern, AntminerOld
from .auradine import Auradine from .auradine import Auradine
from .avalonminer import AvalonMiner from .avalonminer import AvalonMiner
from .bfgminer import BFGMiner from .bfgminer import BFGMiner
from .bitaxe import BitAxe
from .bmminer import BMMiner from .bmminer import BMMiner
from .braiins_os import BOSer, BOSMiner from .braiins_os import BOSer, BOSMiner
from .btminer import BTMiner from .btminer import BTMiner
from .cgminer import CGMiner from .cgminer import CGMiner
from .epic import ePIC from .epic import ePIC
from .goldshell import GoldshellMiner from .goldshell import GoldshellMiner
from .hammer import BlackMiner
from .hiveon import Hiveon from .hiveon import Hiveon
from .iceriver import IceRiver from .iceriver import IceRiver
from .innosilicon import Innosilicon from .innosilicon import Innosilicon
from .luxminer import LUXMiner from .luxminer import LUXMiner
from .marathon import MaraMiner from .marathon import MaraMiner
from .unknown import UnknownMiner
from .vnish import VNish from .vnish import VNish
from .whatsminer import M2X, M3X, M5X, M6X from .whatsminer import M2X, M3X, M5X, M6X

View File

@@ -597,49 +597,44 @@ class AntminerOld(CGMiner):
pass pass
if rpc_stats is not None: if rpc_stats is not None:
try: board_offset = -1
board_offset = -1 boards = rpc_stats["STATS"]
boards = rpc_stats["STATS"] if len(boards) > 1:
if len(boards) > 1: for board_num in range(1, 16, 5):
for board_num in range(1, 16, 5): for _b_num in range(5):
for _b_num in range(5): b = boards[1].get(f"chain_acn{board_num + _b_num}")
b = boards[1].get(f"chain_acn{board_num + _b_num}")
if b and not b == 0 and board_offset == -1: if b and not b == 0 and board_offset == -1:
board_offset = board_num board_offset = board_num
if board_offset == -1: if board_offset == -1:
board_offset = 1 board_offset = 1
for i in range( for i in range(board_offset, board_offset + self.expected_hashboards):
board_offset, board_offset + self.expected_hashboards hashboard = HashBoard(
): slot=i - board_offset, expected_chips=self.expected_chips
hashboard = HashBoard( )
slot=i - board_offset, expected_chips=self.expected_chips
)
chip_temp = boards[1].get(f"temp{i}") chip_temp = boards[1].get(f"temp{i}")
if chip_temp: if chip_temp:
hashboard.chip_temp = round(chip_temp) hashboard.chip_temp = round(chip_temp)
temp = boards[1].get(f"temp2_{i}") temp = boards[1].get(f"temp2_{i}")
if temp: if temp:
hashboard.temp = round(temp) hashboard.temp = round(temp)
hashrate = boards[1].get(f"chain_rate{i}") hashrate = boards[1].get(f"chain_rate{i}")
if hashrate: if hashrate:
hashboard.hashrate = AlgoHashRate.SHA256( hashboard.hashrate = AlgoHashRate.SHA256(
hashrate, HashUnit.SHA256.GH float(hashrate), 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}")
if chips: if chips:
hashboard.chips = chips hashboard.chips = chips
hashboard.missing = False hashboard.missing = False
if (not chips) or (not chips > 0): if (not chips) or (not chips > 0):
hashboard.missing = True hashboard.missing = True
hashboards.append(hashboard) hashboards.append(hashboard)
except (LookupError, ValueError, TypeError):
pass
return hashboards return hashboards

View File

@@ -193,7 +193,14 @@ class Auradine(StockFirmware):
for key in conf.keys(): for key in conf.keys():
await self.web.send_command(command=key, **conf[key]) await self.web.send_command(command=key, **conf[key])
async def upgrade_firmware(self, *, url: str = None, version: str = "latest", keep_settings: bool = False, **kwargs) -> bool: async def upgrade_firmware(
self,
*,
url: str = None,
version: str = "latest",
keep_settings: bool = False,
**kwargs,
) -> bool:
""" """
Upgrade the firmware of the Auradine device. Upgrade the firmware of the Auradine device.
@@ -209,7 +216,9 @@ class Auradine(StockFirmware):
logging.info("Starting firmware upgrade process.") logging.info("Starting firmware upgrade process.")
if not url and not version: if not url and not version:
raise ValueError("Either URL or version must be provided for firmware upgrade.") raise ValueError(
"Either URL or version must be provided for firmware upgrade."
)
if url: if url:
result = await self.web.firmware_upgrade(url=url) result = await self.web.firmware_upgrade(url=url)
@@ -220,11 +229,15 @@ class Auradine(StockFirmware):
logging.info("Firmware upgrade process completed successfully.") logging.info("Firmware upgrade process completed successfully.")
return True return True
else: else:
logging.error(f"Firmware upgrade failed: {result.get('error', 'Unknown error')}") logging.error(
f"Firmware upgrade failed: {result.get('error', 'Unknown error')}"
)
return False return False
except Exception as e: except Exception as e:
logging.error(f"An error occurred during the firmware upgrade process: {str(e)}") logging.error(
f"An error occurred during the firmware upgrade process: {str(e)}"
)
return False return False
################################################## ##################################################

View File

@@ -18,11 +18,11 @@ 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.pools import PoolMetrics, PoolUrl
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.firmware import StockFirmware from pyasic.miners.device.firmware import StockFirmware
from pyasic.rpc.bfgminer import BFGMinerRPCAPI from pyasic.rpc.bfgminer import BFGMinerRPCAPI
from pyasic.data.pools import PoolMetrics, PoolUrl
BFGMINER_DATA_LOC = DataLocations( BFGMINER_DATA_LOC = DataLocations(
**{ **{
@@ -263,4 +263,4 @@ class BFGMiner(StockFirmware):
expected_rate, HashUnit.SHA256.from_str(rate_unit) expected_rate, HashUnit.SHA256.from_str(rate_unit)
).into(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -18,6 +18,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.pools import PoolMetrics, PoolUrl
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.firmware import StockFirmware from pyasic.miners.device.firmware import StockFirmware
@@ -53,6 +54,10 @@ BMMINER_DATA_LOC = DataLocations(
"_get_uptime", "_get_uptime",
[RPCAPICommand("rpc_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_pools", "pools")],
),
} }
) )
@@ -258,3 +263,33 @@ class BMMiner(StockFirmware):
return int(rpc_stats["STATS"][1]["Elapsed"]) return int(rpc_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:
pass pass
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
if rpc_pools is None:
try:
rpc_pools = await self.rpc.pools()
except APIError:
pass
pools_data = []
if rpc_pools is not None:
try:
pools = rpc_pools.get("POOLS", [])
for pool_info in pools:
url = pool_info.get("URL")
pool_url = PoolUrl.from_str(url) if url else None
pool_data = PoolMetrics(
accepted=pool_info.get("Accepted"),
rejected=pool_info.get("Rejected"),
get_failures=pool_info.get("Get Failures"),
remote_failures=pool_info.get("Remote Failures"),
active=pool_info.get("Stratum Active"),
alive=pool_info.get("Status") == "Alive",
url=pool_url,
user=pool_info.get("User"),
index=pool_info.get("POOL"),
)
pools_data.append(pool_data)
except LookupError:
pass
return pools_data

View File

@@ -23,11 +23,11 @@ import aiofiles
from pyasic.config import MinerConfig, MiningModeConfig from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
from pyasic.data.error_codes import MinerErrorData, WhatsminerError from pyasic.data.error_codes import MinerErrorData, WhatsminerError
from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.firmware import StockFirmware from pyasic.miners.device.firmware import StockFirmware
from pyasic.rpc.btminer import BTMinerRPCAPI from pyasic.rpc.btminer import BTMinerRPCAPI
from pyasic.data.pools import PoolMetrics, PoolUrl
BTMINER_DATA_LOC = DataLocations( BTMINER_DATA_LOC = DataLocations(
**{ **{
@@ -113,7 +113,7 @@ BTMINER_DATA_LOC = DataLocations(
str(DataOptions.POOLS): DataFunction( str(DataOptions.POOLS): DataFunction(
"_get_pools", "_get_pools",
[RPCAPICommand("rpc_pools", "pools")], [RPCAPICommand("rpc_pools", "pools")],
) ),
} }
) )

View File

@@ -14,15 +14,15 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from typing import Optional, List from typing import List, Optional
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import AlgoHashRate, HashUnit from pyasic.data import AlgoHashRate, HashUnit
from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.firmware import StockFirmware from pyasic.miners.device.firmware import StockFirmware
from pyasic.rpc.cgminer import CGMinerRPCAPI from pyasic.rpc.cgminer import CGMinerRPCAPI
from pyasic.data.pools import PoolMetrics, PoolUrl
CGMINER_DATA_LOC = DataLocations( CGMINER_DATA_LOC = DataLocations(
**{ **{

View File

@@ -454,8 +454,9 @@ class ePIC(ePICFirmware):
except LookupError: except LookupError:
pass pass
async def upgrade_firmware(self, file: Path | str, keep_settings: bool = True) -> bool: async def upgrade_firmware(
self, file: Path | str, keep_settings: bool = True
) -> bool:
""" """
Upgrade the firmware of the ePIC miner device. Upgrade the firmware of the ePIC miner device.
@@ -466,4 +467,4 @@ class ePIC(ePICFirmware):
Returns: Returns:
bool: Whether the firmware update succeeded. bool: Whether the firmware update succeeded.
""" """
return await self.web.system_update(file=file, keep_settings=keep_settings) return await self.web.system_update(file=file, keep_settings=keep_settings)

View File

@@ -0,0 +1,508 @@
# ------------------------------------------------------------------------------
# Copyright 2022 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from typing import List, Optional
from pyasic import MinerConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.errors import APIError
from pyasic.miners.data import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.miners.device.firmware import StockFirmware
from pyasic.rpc.ccminer import CCMinerRPCAPI
from pyasic.web.hammer import HammerWebAPI
HAMMER_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"_get_mac",
[WebAPICommand("web_get_system_info", "get_system_info")],
),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver",
[RPCAPICommand("rpc_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver",
[RPCAPICommand("rpc_version", "version")],
),
str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname",
[WebAPICommand("web_get_system_info", "get_system_info")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("rpc_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.ERRORS): DataFunction(
"_get_errors",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light",
[WebAPICommand("web_get_blink_status", "get_blink_status")],
),
str(DataOptions.IS_MINING): DataFunction(
"_is_mining",
[WebAPICommand("web_get_conf", "get_miner_conf")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_pools", "pools")],
),
}
)
class BlackMiner(StockFirmware):
"""Handler for Hammer miners."""
_rpc_cls = CCMinerRPCAPI
rpc: CCMinerRPCAPI
_web_cls = HammerWebAPI
web: HammerWebAPI
data_locations = HAMMER_DATA_LOC
async def get_config(self) -> MinerConfig:
data = await self.web.get_miner_conf()
if data:
self.config = MinerConfig.from_hammer(data)
return self.config
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
self.config = config
await self.web.set_miner_conf(config.as_hammer(user_suffix=user_suffix))
async def fault_light_on(self) -> bool:
data = await self.web.blink(blink=True)
if data:
if data.get("code") == "B000":
self.light = True
return self.light
async def fault_light_off(self) -> bool:
data = await self.web.blink(blink=False)
if data:
if data.get("code") == "B100":
self.light = False
return self.light
async def reboot(self) -> bool:
data = await self.web.reboot()
if data:
return True
return False
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
if rpc_version is None:
try:
rpc_version = await self.rpc.version()
except APIError:
pass
if rpc_version is not None:
try:
self.api_ver = rpc_version["VERSION"][0]["API"]
except LookupError:
pass
return self.api_ver
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
if rpc_version is None:
try:
rpc_version = await self.rpc.version()
except APIError:
pass
if rpc_version is not None:
try:
self.fw_ver = rpc_version["VERSION"][0]["CompileTime"]
except LookupError:
pass
return self.fw_ver
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
# get hr from API
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
except APIError:
pass
if rpc_summary is not None:
try:
return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH
).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError):
pass
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
hashboards = []
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_stats is not None:
try:
board_offset = -1
boards = rpc_stats["STATS"]
if len(boards) > 1:
for board_num in range(1, 16, 5):
for _b_num in range(5):
b = boards[1].get(f"chain_acn{board_num + _b_num}")
if b and not b == 0 and board_offset == -1:
board_offset = board_num
if board_offset == -1:
board_offset = 1
real_slots = []
for i in range(board_offset, board_offset + 4):
try:
key = f"chain_acs{i}"
if boards[1].get(key, "") != "":
real_slots.append(i)
except LookupError:
pass
if len(real_slots) < 3:
real_slots = list(
range(board_offset, board_offset + self.expected_hashboards)
)
for i in real_slots:
hashboard = HashBoard(
slot=i - board_offset, expected_chips=self.expected_chips
)
chip_temp = boards[1].get(f"temp{i}")
if chip_temp:
hashboard.chip_temp = round(chip_temp)
temp = boards[1].get(f"temp2_{i}")
if temp:
hashboard.temp = round(temp)
hashrate = boards[1].get(f"chain_rate{i}")
if hashrate:
hashboard.hashrate = AlgoHashRate.SHA256(
hashrate, HashUnit.SHA256.GH
).into(self.algo.unit.default)
chips = boards[1].get(f"chain_acn{i}")
if chips:
hashboard.chips = chips
hashboard.missing = False
if (not chips) or (not chips > 0):
hashboard.missing = True
hashboards.append(hashboard)
except (LookupError, ValueError, TypeError):
pass
return hashboards
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
fans = [Fan() for _ in range(self.expected_fans)]
if rpc_stats is not None:
try:
fan_offset = -1
for fan_num in range(1, 8, 4):
for _f_num in range(4):
f = rpc_stats["STATS"][1].get(f"fan{fan_num + _f_num}", 0)
if f and not f == 0 and fan_offset == -1:
fan_offset = fan_num
if fan_offset == -1:
fan_offset = 1
for fan in range(self.expected_fans):
fans[fan].speed = rpc_stats["STATS"][1].get(
f"fan{fan_offset+fan}", 0
)
except LookupError:
pass
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]:
if web_get_system_info is None:
try:
web_get_system_info = await self.web.get_system_info()
except APIError:
pass
if web_get_system_info is not None:
try:
return web_get_system_info["hostname"]
except KeyError:
pass
async def _get_mac(self, web_get_system_info: dict = None) -> Optional[str]:
if web_get_system_info is None:
try:
web_get_system_info = await self.web.get_system_info()
except APIError:
pass
if web_get_system_info is not None:
try:
return web_get_system_info["macaddr"]
except KeyError:
pass
try:
data = await self.web.get_network_info()
if data:
return data["macaddr"]
except KeyError:
pass
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
pass
errors = []
if web_summary is not None:
try:
for item in web_summary["SUMMARY"][0]["status"]:
try:
if not item["status"] == "s":
errors.append(X19Error(item["msg"]))
except KeyError:
continue
except LookupError:
pass
return errors
async def _get_fault_light(
self, web_get_blink_status: dict = None
) -> Optional[bool]:
if self.light:
return self.light
if web_get_blink_status is None:
try:
web_get_blink_status = await self.web.get_blink_status()
except APIError:
pass
if web_get_blink_status is not None:
try:
self.light = web_get_blink_status["blink"]
except KeyError:
pass
return self.light
async def _get_expected_hashrate(
self, rpc_stats: dict = None
) -> Optional[AlgoHashRate]:
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 set_static_ip(
self,
ip: str,
dns: str,
gateway: str,
subnet_mask: str = "255.255.255.0",
hostname: str = None,
):
if not hostname:
hostname = await self.get_hostname()
await self.web.set_network_conf(
ip=ip,
dns=dns,
gateway=gateway,
subnet_mask=subnet_mask,
hostname=hostname,
protocol=2,
)
async def set_dhcp(self, hostname: str = None):
if not hostname:
hostname = await self.get_hostname()
await self.web.set_network_conf(
ip="", dns="", gateway="", subnet_mask="", hostname=hostname, protocol=1
)
async def set_hostname(self, hostname: str):
cfg = await self.web.get_network_info()
dns = cfg["conf_dnsservers"]
gateway = cfg["conf_gateway"]
ip = cfg["conf_ipaddress"]
subnet_mask = cfg["conf_netmask"]
protocol = 1 if cfg["conf_nettype"] == "DHCP" else 2
await self.web.set_network_conf(
ip=ip,
dns=dns,
gateway=gateway,
subnet_mask=subnet_mask,
hostname=hostname,
protocol=protocol,
)
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
if web_get_conf is None:
try:
web_get_conf = await self.web.get_miner_conf()
except APIError:
pass
if web_get_conf is not None:
try:
if str(web_get_conf["bitmain-work-mode"]).isdigit():
return (
False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
)
return False
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_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
if rpc_pools is None:
try:
rpc_pools = await self.rpc.pools()
except APIError:
pass
pools_data = []
if rpc_pools is not None:
try:
pools = rpc_pools.get("POOLS", [])
for pool_info in pools:
url = pool_info.get("URL")
pool_url = PoolUrl.from_str(url) if url else None
pool_data = PoolMetrics(
accepted=pool_info.get("Accepted"),
rejected=pool_info.get("Rejected"),
get_failures=pool_info.get("Get Failures"),
remote_failures=pool_info.get("Remote Failures"),
active=pool_info.get("Stratum Active"),
alive=pool_info.get("Status") == "Alive",
url=pool_url,
user=pool_info.get("User"),
index=pool_info.get("POOL"),
)
pools_data.append(pool_data)
except LookupError:
pass
return pools_data

View File

@@ -1,6 +1,8 @@
from typing import List, Optional from typing import List, Optional
from pyasic import MinerConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.device import MinerAlgo from pyasic.device import MinerAlgo
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
@@ -41,6 +43,10 @@ ICERIVER_DATA_LOC = DataLocations(
"_get_uptime", "_get_uptime",
[WebAPICommand("web_userpanel", "userpanel")], [WebAPICommand("web_userpanel", "userpanel")],
), ),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[WebAPICommand("web_userpanel", "userpanel")],
),
} }
) )
@@ -67,6 +73,11 @@ class IceRiver(StockFirmware):
return False return False
return True return True
async def get_config(self) -> MinerConfig:
web_userpanel = await self.web.userpanel()
return MinerConfig.from_iceriver(web_userpanel)
async def _get_fans(self, web_userpanel: dict = None) -> List[Fan]: async def _get_fans(self, web_userpanel: dict = None) -> List[Fan]:
if web_userpanel is None: if web_userpanel is None:
try: try:
@@ -76,7 +87,7 @@ 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["fans"]] return [Fan(spd) for spd in web_userpanel["userpanel"]["data"]["fans"]]
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -89,7 +100,9 @@ class IceRiver(StockFirmware):
if web_userpanel is not None: if web_userpanel is not None:
try: try:
return web_userpanel["mac"].upper().replace("-", ":") return (
web_userpanel["userpanel"]["data"]["mac"].upper().replace("-", ":")
)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -102,7 +115,7 @@ class IceRiver(StockFirmware):
if web_userpanel is not None: if web_userpanel is not None:
try: try:
return web_userpanel["host"] return web_userpanel["userpanel"]["data"]["host"]
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -115,9 +128,13 @@ class IceRiver(StockFirmware):
if web_userpanel is not None: if web_userpanel is not None:
try: try:
base_unit = web_userpanel["unit"] base_unit = web_userpanel["userpanel"]["data"]["unit"]
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
float(web_userpanel["rtpow"].replace(base_unit, "")), float(
web_userpanel["userpanel"]["data"]["rtpow"].replace(
base_unit, ""
)
),
unit=MinerAlgo.SHA256.unit.from_str(base_unit + "H"), unit=MinerAlgo.SHA256.unit.from_str(base_unit + "H"),
).into(MinerAlgo.SHA256.unit.default) ).into(MinerAlgo.SHA256.unit.default)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
@@ -132,7 +149,7 @@ class IceRiver(StockFirmware):
if web_userpanel is not None: if web_userpanel is not None:
try: try:
return web_userpanel["locate"] return web_userpanel["userpanel"]["data"]["locate"]
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
return False return False
@@ -146,7 +163,7 @@ class IceRiver(StockFirmware):
if web_userpanel is not None: if web_userpanel is not None:
try: try:
return web_userpanel["powstate"] return web_userpanel["userpanel"]["data"]["powstate"]
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
@@ -164,7 +181,7 @@ class IceRiver(StockFirmware):
if web_userpanel is not None: if web_userpanel is not None:
try: try:
for board in web_userpanel["boards"]: for board in web_userpanel["userpanel"]["data"]["boards"]:
idx = int(board["no"] - 1) idx = int(board["no"] - 1)
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"])
@@ -186,7 +203,7 @@ class IceRiver(StockFirmware):
if web_userpanel is not None: if web_userpanel is not None:
try: try:
runtime = web_userpanel["runtime"] runtime = web_userpanel["userpanel"]["data"]["runtime"]
days, hours, minutes, seconds = runtime.split(":") days, hours, minutes, seconds = runtime.split(":")
return ( return (
(int(days) * 24 * 60 * 60) (int(days) * 24 * 60 * 60)
@@ -196,3 +213,36 @@ class IceRiver(StockFirmware):
) )
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
async def _get_pools(self, web_userpanel: dict = None) -> List[PoolMetrics]:
if web_userpanel is None:
try:
web_userpanel = await self.web.userpanel()
except APIError:
pass
pools_data = []
if web_userpanel is not None:
try:
pools = web_userpanel["userpanel"]["data"]["pools"]
for pool_info in pools:
pool_num = pool_info.get("no")
if pool_num is not None:
pool_num = int(pool_num)
if pool_info["addr"] == "":
continue
url = pool_info.get("addr")
pool_url = PoolUrl.from_str(url) if url else None
pool_data = PoolMetrics(
accepted=pool_info.get("accepted"),
rejected=pool_info.get("rejected"),
active=pool_info.get("connect"),
alive=int(pool_info.get("state", 0)) == 1,
url=pool_url,
user=pool_info.get("user"),
index=pool_num,
)
pools_data.append(pool_data)
except LookupError:
pass
return pools_data

View File

@@ -19,6 +19,7 @@ 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 from pyasic.data.error_codes import MinerErrorData
from pyasic.data.error_codes.innosilicon import InnosiliconError from pyasic.data.error_codes.innosilicon import InnosiliconError
from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends import CGMiner from pyasic.miners.backends import CGMiner
from pyasic.miners.data import ( from pyasic.miners.data import (
@@ -29,7 +30,6 @@ from pyasic.miners.data import (
WebAPICommand, WebAPICommand,
) )
from pyasic.web.innosilicon import InnosiliconWebAPI from pyasic.web.innosilicon import InnosiliconWebAPI
from pyasic.data.pools import PoolMetrics, PoolUrl
INNOSILICON_DATA_LOC = DataLocations( INNOSILICON_DATA_LOC = DataLocations(
**{ **{
@@ -92,9 +92,8 @@ INNOSILICON_DATA_LOC = DataLocations(
[RPCAPICommand("rpc_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.POOLS): DataFunction( str(DataOptions.POOLS): DataFunction(
"_get_pools", "_get_pools", [RPCAPICommand("rpc_pools", "pools")]
[RPCAPICommand("rpc_pools", "pools")] ),
)
} }
) )
@@ -116,7 +115,7 @@ class Innosilicon(CGMiner):
except APIError: except APIError:
return self.config return self.config
self.config = MinerConfig.from_inno([pools]) self.config = MinerConfig.from_inno(pools["pools"])
return self.config return self.config
async def reboot(self) -> bool: async def reboot(self) -> bool:

View File

@@ -13,8 +13,8 @@
# 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 typing import List, Optional
import logging import logging
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
@@ -56,6 +56,15 @@ LUXMINER_DATA_LOC = DataLocations(
str(DataOptions.POOLS): DataFunction( str(DataOptions.POOLS): DataFunction(
"_get_pools", [RPCAPICommand("rpc_pools", "pools")] "_get_pools", [RPCAPICommand("rpc_pools", "pools")]
), ),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", [RPCAPICommand("rpc_version", "version")]
),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", [RPCAPICommand("rpc_version", "version")]
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light", [RPCAPICommand("rpc_config", "config")]
),
} }
) )
@@ -68,25 +77,9 @@ class LUXMiner(LuxOSFirmware):
data_locations = LUXMINER_DATA_LOC data_locations = LUXMINER_DATA_LOC
async def _get_session(self) -> Optional[str]:
try:
data = await self.rpc.session()
if not data["SESSION"][0]["SessionID"] == "":
return data["SESSION"][0]["SessionID"]
except APIError:
pass
try:
data = await self.rpc.logon()
return data["SESSION"][0]["SessionID"]
except (LookupError, APIError):
return
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
try: try:
session_id = await self._get_session() await self.rpc.ledset("red", "blink")
if session_id:
await self.rpc.ledset(session_id, "red", "blink")
return True return True
except (APIError, LookupError): except (APIError, LookupError):
pass pass
@@ -94,9 +87,7 @@ class LUXMiner(LuxOSFirmware):
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
try: try:
session_id = await self._get_session() await self.rpc.ledset("red", "off")
if session_id:
await self.rpc.ledset(session_id, "red", "off")
return True return True
except (APIError, LookupError): except (APIError, LookupError):
pass pass
@@ -107,9 +98,7 @@ class LUXMiner(LuxOSFirmware):
async def restart_luxminer(self) -> bool: async def restart_luxminer(self) -> bool:
try: try:
session_id = await self._get_session() await self.rpc.resetminer()
if session_id:
await self.rpc.resetminer(session_id)
return True return True
except (APIError, LookupError): except (APIError, LookupError):
pass pass
@@ -117,9 +106,7 @@ class LUXMiner(LuxOSFirmware):
async def stop_mining(self) -> bool: async def stop_mining(self) -> bool:
try: try:
session_id = await self._get_session() await self.rpc.sleep()
if session_id:
await self.rpc.curtail(session_id)
return True return True
except (APIError, LookupError): except (APIError, LookupError):
pass pass
@@ -127,25 +114,27 @@ class LUXMiner(LuxOSFirmware):
async def resume_mining(self) -> bool: async def resume_mining(self) -> bool:
try: try:
session_id = await self._get_session() await self.rpc.wakeup()
if session_id:
await self.rpc.wakeup(session_id)
return True return True
except (APIError, LookupError): except (APIError, LookupError):
pass pass
async def reboot(self) -> bool: async def reboot(self) -> bool:
try: try:
session_id = await self._get_session() await self.rpc.rebootdevice()
if session_id:
await self.rpc.rebootdevice(session_id)
return True return True
except (APIError, LookupError): except (APIError, LookupError):
pass pass
return False return False
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
return self.config data = await self.rpc.multicommand("tempctrl", "fans", "pools", "groups")
return MinerConfig.from_luxos(
rpc_tempctrl=data.get("tempctrl", [{}])[0],
rpc_fans=data.get("fans", [{}])[0],
rpc_pools=data.get("pools", [{}])[0],
rpc_groups=data.get("groups", [{}])[0],
)
async def upgrade_firmware(self) -> bool: async def upgrade_firmware(self) -> bool:
""" """
@@ -168,20 +157,17 @@ class LUXMiner(LuxOSFirmware):
################################################## ##################################################
async def _get_mac(self, rpc_config: dict = None) -> Optional[str]: async def _get_mac(self, rpc_config: dict = None) -> Optional[str]:
mac = None
if rpc_config is None: if rpc_config is None:
try: try:
rpc_config = await self.rpc.config() rpc_config = await self.rpc.config()
except APIError: except APIError:
return None pass
if rpc_config is not None: if rpc_config is not None:
try: try:
mac = rpc_config["CONFIG"][0]["MACAddr"] return rpc_config["CONFIG"][0]["MACAddr"].upper()
except KeyError: except KeyError:
return None pass
return mac
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]: async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
if rpc_summary is None: if rpc_summary is None:
@@ -199,59 +185,47 @@ class LUXMiner(LuxOSFirmware):
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)
for idx in range(self.expected_hashboards)
]
if rpc_stats is None: if rpc_stats is None:
try: try:
rpc_stats = await self.rpc.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if rpc_stats is not None: if rpc_stats is not None:
try: try:
board_offset = -1 # TODO: bugged on S9 because of index issues, fix later.
boards = rpc_stats["STATS"] board_stats = rpc_stats["STATS"][1]
if len(boards) > 1: for idx in range(3):
for board_num in range(1, 16, 5): board_n = idx + 1
for _b_num in range(5): hashboards[idx].hashrate = AlgoHashRate.SHA256(
b = boards[1].get(f"chain_acn{board_num + _b_num}") float(board_stats[f"chain_rate{board_n}"]), HashUnit.SHA256.GH
).into(self.algo.unit.default)
if b and not b == 0 and board_offset == -1: hashboards[idx].chips = int(board_stats[f"chain_acn{board_n}"])
board_offset = board_num chip_temp_data = list(
if board_offset == -1: filter(
board_offset = 1 lambda x: not x == 0,
map(int, board_stats[f"temp_chip{board_n}"].split("-")),
for i in range(
board_offset, board_offset + self.expected_hashboards
):
hashboard = HashBoard(
slot=i - board_offset, expected_chips=self.expected_chips
) )
)
chip_temp = boards[1].get(f"temp{i}") hashboards[idx].chip_temp = (
if chip_temp: sum([chip_temp_data[0], chip_temp_data[3]]) / 2
hashboard.chip_temp = round(chip_temp) )
board_temp_data = list(
temp = boards[1].get(f"temp2_{i}") filter(
if temp: lambda x: not x == 0,
hashboard.temp = round(temp) map(int, board_stats[f"temp_pcb{board_n}"].split("-")),
)
hashrate = boards[1].get(f"chain_rate{i}") )
if hashrate: hashboards[idx].temp = (
hashboard.hashrate = AlgoHashRate.SHA256( sum([board_temp_data[1], board_temp_data[2]]) / 2
hashrate, HashUnit.SHA256.GH )
).into(self.algo.unit.default) hashboards[idx].missing = False
except LookupError:
chips = boards[1].get(f"chain_acn{i}")
if chips:
hashboard.chips = chips
hashboard.missing = False
if (not chips) or (not chips > 0):
hashboard.missing = True
hashboards.append(hashboard)
except (LookupError, ValueError, TypeError):
pass pass
return hashboards return hashboards
async def _get_wattage(self, rpc_power: dict = None) -> Optional[int]: async def _get_wattage(self, rpc_power: dict = None) -> Optional[int]:
@@ -319,6 +293,45 @@ class LUXMiner(LuxOSFirmware):
except LookupError: except LookupError:
pass pass
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
if rpc_version is None:
try:
rpc_version = await self.rpc.version()
except APIError:
pass
if rpc_version is not None:
try:
return rpc_version["VERSION"][0]["Miner"]
except LookupError:
pass
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
if rpc_version is None:
try:
rpc_version = await self.rpc.version()
except APIError:
pass
if rpc_version is not None:
try:
return rpc_version["VERSION"][0]["API"]
except LookupError:
pass
async def _get_fault_light(self, rpc_config: dict = None) -> Optional[bool]:
if rpc_config is None:
try:
rpc_config = await self.rpc.config()
except APIError:
pass
if rpc_config is not None:
try:
return not rpc_config["CONFIG"][0]["RedLed"] == "off"
except LookupError:
pass
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]: async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
if rpc_pools is None: if rpc_pools is None:
try: try:

View File

@@ -3,13 +3,13 @@ from typing import List, Optional
from pyasic import MinerConfig from pyasic import MinerConfig
from pyasic.config import MiningModeConfig from pyasic.config import MiningModeConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
from pyasic.miners.device.firmware import MaraFirmware from pyasic.miners.device.firmware import MaraFirmware
from pyasic.misc import merge_dicts from pyasic.misc import merge_dicts
from pyasic.rpc.marathon import MaraRPCAPI from pyasic.rpc.marathon import MaraRPCAPI
from pyasic.web.marathon import MaraWebAPI from pyasic.web.marathon import MaraWebAPI
from pyasic.data.pools import PoolMetrics, PoolUrl
MARA_DATA_LOC = DataLocations( MARA_DATA_LOC = DataLocations(
**{ **{
@@ -319,11 +319,14 @@ class MaraMiner(MaraFirmware):
return [] return []
active_pool_index = None active_pool_index = None
highest_priority = float('inf') highest_priority = float("inf")
for pool_info in web_pools: for pool_info in web_pools:
if pool_info.get("status") == "Alive" and pool_info.get("priority", float('inf')) < highest_priority: if (
highest_priority = pool_info.get["priority"] pool_info.get("status") == "Alive"
and pool_info.get("priority", float("inf")) < highest_priority
):
highest_priority = pool_info["priority"]
active_pool_index = pool_info["index"] active_pool_index = pool_info["index"]
pools_data = [] pools_data = []

View File

@@ -237,13 +237,17 @@ class VNish(VNishFirmware, BMMiner):
async def _is_mining(self, web_summary: dict = None) -> Optional[bool]: async def _is_mining(self, web_summary: dict = None) -> Optional[bool]:
if web_summary is None: if web_summary is None:
web_summary = await self.web.summary() try:
web_summary = await self.web.summary()
except APIError:
pass
if web_summary is not None: if web_summary is not None:
try: try:
is_mining = not web_summary["miner"]["miner_status"]["miner_state"] in [ is_mining = not web_summary["miner"]["miner_status"]["miner_state"] in [
"stopped", "stopped",
"shutting-down", "shutting-down",
"failure",
] ]
return is_mining return is_mining
except LookupError: except LookupError:

View File

@@ -560,7 +560,14 @@ class BaseMiner(MinerProtocol):
if self._ssh_cls is not None: if self._ssh_cls is not None:
self.ssh = self._ssh_cls(ip) self.ssh = self._ssh_cls(ip)
async def upgrade_firmware(self, *, file: str = None, url: str = None, version: str = None, keep_settings: bool = True) -> bool: async def upgrade_firmware(
self,
*,
file: str = None,
url: str = None,
version: str = None,
keep_settings: bool = True,
) -> bool:
"""Upgrade the firmware of the miner. """Upgrade the firmware of the miner.
Parameters: Parameters:
@@ -574,4 +581,5 @@ class BaseMiner(MinerProtocol):
""" """
return False return False
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner) AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)

View File

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

View File

@@ -43,4 +43,4 @@ class LuxOSFirmware(BaseMiner):
class MaraFirmware(BaseMiner): class MaraFirmware(BaseMiner):
firmware = MinerFirmware.MARATHON firmware = MinerFirmware.MARATHON

View File

@@ -52,3 +52,7 @@ class BitAxeMake(BaseMiner):
class IceRiverMake(BaseMiner): class IceRiverMake(BaseMiner):
make = MinerMake.ICERIVER make = MinerMake.ICERIVER
class HammerMake(BaseMiner):
make = MinerMake.HAMMER

View File

@@ -19,6 +19,7 @@ from .auradine import *
from .avalonminer import * from .avalonminer import *
from .epic import * from .epic import *
from .goldshell import * from .goldshell import *
from .hammer import *
from .iceriver import * from .iceriver import *
from .innosilicon import * from .innosilicon import *
from .whatsminer import * from .whatsminer import *

View File

@@ -21,3 +21,9 @@ class Z15(AntMinerMake):
raw_model = MinerModel.ANTMINER.Z15 raw_model = MinerModel.ANTMINER.Z15
expected_chips = 3 expected_chips = 3
class Z15Pro(AntMinerMake):
raw_model = MinerModel.ANTMINER.Z15Pro
expected_chips = 6

View File

@@ -13,4 +13,4 @@
# 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 .Z15 import Z15 from .Z15 import Z15, Z15Pro

View File

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

View File

@@ -13,4 +13,5 @@
# 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 .K7 import K7
from .L7 import L7 from .L7 import L7

View File

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

View File

@@ -14,6 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .D9 import D9
from .E9 import E9Pro from .E9 import E9Pro
from .S9 import S9, S9i, S9j from .S9 import S9, S9i, S9j
from .T9 import T9 from .T9 import T9

View File

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

View File

@@ -0,0 +1 @@
from .D10 import D10

View File

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

View File

@@ -0,0 +1,37 @@
# ------------------------------------------------------------------------------
# Copyright 2024 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.device.models import MinerModel
from pyasic.miners.device.makes import IceRiverMake
class KS5(IceRiverMake):
raw_model = MinerModel.ICERIVER.KS5
expected_fans = 4
expected_chips = 92
class KS5L(IceRiverMake):
raw_model = MinerModel.ICERIVER.KS5L
expected_fans = 4
expected_chips = 18
class KS5M(IceRiverMake):
raw_model = MinerModel.ICERIVER.KS5M
expected_fans = 4

View File

@@ -2,3 +2,4 @@ from .KS0 import KS0
from .KS1 import KS1 from .KS1 import KS1
from .KS2 import KS2 from .KS2 import KS2
from .KS3 import KS3, KS3L, KS3M from .KS3 import KS3, KS3L, KS3M
from .KS5 import KS5, KS5L, KS5M

View File

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

View File

@@ -19,3 +19,6 @@ from pyasic.miners.device.makes import InnosiliconMake
class A11MX(InnosiliconMake): class A11MX(InnosiliconMake):
raw_model = MinerModel.INNOSILICON.A11MX raw_model = MinerModel.INNOSILICON.A11MX
expected_hashboards = 4
expected_chips = 8

View File

@@ -13,4 +13,5 @@
# 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 .A11 import *
from .A11M import * from .A11M import *

View File

@@ -78,6 +78,12 @@ class M50VH80(WhatsMinerMake):
expected_chips = 111 expected_chips = 111
class M50VH90(WhatsMinerMake):
raw_model = MinerModel.WHATSMINER.M50VH90
expected_chips = 117
class M50VJ10(WhatsMinerMake): class M50VJ10(WhatsMinerMake):
raw_model = MinerModel.WHATSMINER.M50VJ10 raw_model = MinerModel.WHATSMINER.M50VJ10

View File

@@ -25,6 +25,7 @@ from .M50 import (
M50VH60, M50VH60,
M50VH70, M50VH70,
M50VH80, M50VH80,
M50VH90,
M50VJ10, M50VJ10,
M50VJ20, M50VJ20,
M50VJ30, M50VJ30,

View File

@@ -32,13 +32,12 @@ from pyasic.miners.antminer import *
from pyasic.miners.auradine import * from pyasic.miners.auradine import *
from pyasic.miners.avalonminer import * from pyasic.miners.avalonminer import *
from pyasic.miners.backends import * from pyasic.miners.backends import *
from pyasic.miners.backends.bitaxe import BitAxe
from pyasic.miners.backends.unknown import UnknownMiner
from pyasic.miners.base import AnyMiner from pyasic.miners.base import AnyMiner
from pyasic.miners.bitaxe import * from pyasic.miners.bitaxe import *
from pyasic.miners.blockminer import * from pyasic.miners.blockminer import *
from pyasic.miners.device.makes import * from pyasic.miners.device.makes import *
from pyasic.miners.goldshell import * from pyasic.miners.goldshell import *
from pyasic.miners.hammer import *
from pyasic.miners.iceriver import * from pyasic.miners.iceriver import *
from pyasic.miners.innosilicon import * from pyasic.miners.innosilicon import *
from pyasic.miners.whatsminer import * from pyasic.miners.whatsminer import *
@@ -59,6 +58,7 @@ class MinerTypes(enum.Enum):
MARATHON = 11 MARATHON = 11
BITAXE = 12 BITAXE = 12
ICERIVER = 13 ICERIVER = 13
HAMMER = 14
MINER_CLASSES = { MINER_CLASSES = {
@@ -72,12 +72,15 @@ MINER_CLASSES = {
"ANTMINER DR5": CGMinerDR5, "ANTMINER DR5": CGMinerDR5,
"ANTMINER KS5": BMMinerKS5, "ANTMINER KS5": BMMinerKS5,
"ANTMINER L7": BMMinerL7, "ANTMINER L7": BMMinerL7,
"ANTMINER K7": BMMinerK7,
"ANTMINER E9 PRO": BMMinerE9Pro, "ANTMINER E9 PRO": BMMinerE9Pro,
"ANTMINER D9": BMMinerD9,
"ANTMINER S9": BMMinerS9, "ANTMINER S9": BMMinerS9,
"ANTMINER S9I": BMMinerS9i, "ANTMINER S9I": BMMinerS9i,
"ANTMINER S9J": BMMinerS9j, "ANTMINER S9J": BMMinerS9j,
"ANTMINER T9": BMMinerT9, "ANTMINER T9": BMMinerT9,
"ANTMINER Z15": CGMinerZ15, "ANTMINER Z15": CGMinerZ15,
"ANTMINER Z15 PRO": BMMinerZ15Pro,
"ANTMINER S17": BMMinerS17, "ANTMINER S17": BMMinerS17,
"ANTMINER S17+": BMMinerS17Plus, "ANTMINER S17+": BMMinerS17Plus,
"ANTMINER S17 PRO": BMMinerS17Pro, "ANTMINER S17 PRO": BMMinerS17Pro,
@@ -275,6 +278,7 @@ MINER_CLASSES = {
"M50VH60": BTMinerM50VH60, "M50VH60": BTMinerM50VH60,
"M50VH70": BTMinerM50VH70, "M50VH70": BTMinerM50VH70,
"M50VH80": BTMinerM50VH80, "M50VH80": BTMinerM50VH80,
"M50VH90": BTMinerM50VH90,
"M50VJ10": BTMinerM50VJ10, "M50VJ10": BTMinerM50VJ10,
"M50VJ20": BTMinerM50VJ20, "M50VJ20": BTMinerM50VJ20,
"M50VJ30": BTMinerM50VJ30, "M50VJ30": BTMinerM50VJ30,
@@ -342,6 +346,7 @@ MINER_CLASSES = {
None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}), None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}),
"T3H+": InnosiliconT3HPlus, "T3H+": InnosiliconT3HPlus,
"A10X": InnosiliconA10X, "A10X": InnosiliconA10X,
"A11": InnosiliconA11,
"A11MX": InnosiliconA11MX, "A11MX": InnosiliconA11MX,
}, },
MinerTypes.GOLDSHELL: { MinerTypes.GOLDSHELL: {
@@ -395,6 +400,7 @@ MINER_CLASSES = {
"ANTMINER S19 PRO": VNishS19Pro, "ANTMINER S19 PRO": VNishS19Pro,
"ANTMINER S19J": VNishS19j, "ANTMINER S19J": VNishS19j,
"ANTMINER S19J PRO": VNishS19jPro, "ANTMINER S19J PRO": VNishS19jPro,
"ANTMINER S19J PRO BB": VNishS19jPro,
"ANTMINER S19A": VNishS19a, "ANTMINER S19A": VNishS19a,
"ANTMINER S19A PRO": VNishS19aPro, "ANTMINER S19A PRO": VNishS19aPro,
"ANTMINER S19 PRO HYD.": VNishS19ProHydro, "ANTMINER S19 PRO HYD.": VNishS19ProHydro,
@@ -468,6 +474,13 @@ MINER_CLASSES = {
"KS3": IceRiverKS3, "KS3": IceRiverKS3,
"KS3L": IceRiverKS3L, "KS3L": IceRiverKS3L,
"KS3M": IceRiverKS3M, "KS3M": IceRiverKS3M,
"KS5": IceRiverKS5,
"KS5L": IceRiverKS5L,
"KS5M": IceRiverKS5M,
},
MinerTypes.HAMMER: {
None: type("HammerUnknown", (BlackMiner, HammerMake), {}),
"HAMMER D10": HammerD10,
}, },
} }
@@ -546,6 +559,7 @@ class MinerFactory:
MinerTypes.AURADINE: self.get_miner_model_auradine, MinerTypes.AURADINE: self.get_miner_model_auradine,
MinerTypes.MARATHON: self.get_miner_model_marathon, MinerTypes.MARATHON: self.get_miner_model_marathon,
MinerTypes.BITAXE: self.get_miner_model_bitaxe, MinerTypes.BITAXE: self.get_miner_model_bitaxe,
MinerTypes.ICERIVER: self.get_miner_model_iceriver,
} }
fn = miner_model_fns.get(miner_type) fn = miner_model_fns.get(miner_type)
@@ -617,6 +631,10 @@ class MinerFactory:
"www-authenticate", "" "www-authenticate", ""
): ):
return MinerTypes.ANTMINER return MinerTypes.ANTMINER
if web_resp.status_code == 401 and 'realm="blackMiner' in web_resp.headers.get(
"www-authenticate", ""
):
return MinerTypes.HAMMER
if len(web_resp.history) > 0: if len(web_resp.history) > 0:
history_resp = web_resp.history[0] history_resp = web_resp.history[0]
if ( if (
@@ -627,6 +645,8 @@ class MinerFactory:
return MinerTypes.WHATSMINER return MinerTypes.WHATSMINER
if "Braiins OS" in web_text: if "Braiins OS" in web_text:
return MinerTypes.BRAIINS_OS return MinerTypes.BRAIINS_OS
if "Luxor Firmware" in web_text:
return MinerTypes.LUX_OS
if "<TITLE>用户界面</TITLE>" in web_text: if "<TITLE>用户界面</TITLE>" in web_text:
return MinerTypes.ICERIVER return MinerTypes.ICERIVER
if "AxeOS" in web_text: if "AxeOS" in web_text:
@@ -709,10 +729,10 @@ class MinerFactory:
return MinerTypes.BRAIINS_OS return MinerTypes.BRAIINS_OS
if "BTMINER" in upper_data or "BITMICRO" in upper_data: if "BTMINER" in upper_data or "BITMICRO" in upper_data:
return MinerTypes.WHATSMINER return MinerTypes.WHATSMINER
if "HIVEON" in upper_data:
return MinerTypes.HIVEON
if "LUXMINER" in upper_data: if "LUXMINER" in upper_data:
return MinerTypes.LUX_OS return MinerTypes.LUX_OS
if "HIVEON" in upper_data:
return MinerTypes.HIVEON
if "KAONSU" in upper_data: if "KAONSU" in upper_data:
return MinerTypes.MARATHON return MinerTypes.MARATHON
if "ANTMINER" in upper_data and "DEVDETAILS" not in upper_data: if "ANTMINER" in upper_data and "DEVDETAILS" not in upper_data:
@@ -817,12 +837,8 @@ class MinerFactory:
# fix an error with a btminer return having a missing comma. (2023-01-06 version) # fix an error with a btminer return having a missing comma. (2023-01-06 version)
str_data = str_data.replace('""temp0', '","temp0') str_data = str_data.replace('""temp0', '","temp0')
# fix an error with Avalonminers returning inf and nan # fix an error with Avalonminers returning inf and nan
str_data = str_data.replace("info", "1nfo") str_data = str_data.replace('"inf"', "0")
str_data = str_data.replace("inf", "0") str_data = str_data.replace('"nan"', "0")
str_data = str_data.replace("1nfo", "info")
str_data = str_data.replace("nano", "n4no")
str_data = str_data.replace("nan", "0")
str_data = str_data.replace("n4no", "nano")
# fix whatever this garbage from avalonminers is `,"id":1}` # fix whatever this garbage from avalonminers is `,"id":1}`
if str_data.startswith(","): if str_data.startswith(","):
str_data = f"{{{str_data[1:]}" str_data = f"{{{str_data[1:]}"
@@ -941,10 +957,19 @@ class MinerFactory:
async with httpx.AsyncClient(transport=settings.transport()) as session: async with httpx.AsyncClient(transport=settings.transport()) as session:
auth_req = await session.post( auth_req = await session.post(
f"http://{ip}/api/auth", f"http://{ip}/api/auth",
data={"username": "admin", "password": "admin"}, data={
"username": "admin",
"password": settings.get(
"default_innosilicon_web_password", "admin"
),
},
) )
auth = auth_req.json()["jwt"] auth = auth_req.json()["jwt"]
except (httpx.HTTPError, LookupError):
return
try:
async with httpx.AsyncClient(transport=settings.transport()) as session:
web_data = ( web_data = (
await session.post( await session.post(
f"http://{ip}/api/type", f"http://{ip}/api/type",
@@ -955,6 +980,18 @@ class MinerFactory:
return web_data["type"] return web_data["type"]
except (httpx.HTTPError, LookupError): except (httpx.HTTPError, LookupError):
pass pass
try:
async with httpx.AsyncClient(transport=settings.transport()) as session:
web_data = (
await session.post(
f"http://{ip}/overview",
headers={"Authorization": "Bearer " + auth},
data={},
)
).json()
return web_data["type"]
except (httpx.HTTPError, LookupError):
pass
async def get_miner_model_braiins_os(self, ip: str) -> str | None: async def get_miner_model_braiins_os(self, ip: str) -> str | None:
sock_json_data = await self.send_api_command(ip, "devdetails") sock_json_data = await self.send_api_command(ip, "devdetails")
@@ -1101,6 +1138,21 @@ class MinerFactory:
except httpx.HTTPError: except httpx.HTTPError:
pass pass
async def get_miner_model_hammer(self, ip: str) -> str | None:
auth = httpx.DigestAuth(
"root", settings.get("default_hammer_web_password", "root")
)
web_json_data = await self.send_web_command(
ip, "/cgi-bin/get_system_info.cgi", auth=auth
)
try:
miner_model = web_json_data["minertype"]
return miner_model
except (TypeError, LookupError):
pass
miner_factory = MinerFactory() miner_factory = MinerFactory()

View File

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

View File

@@ -0,0 +1,6 @@
from pyasic.miners.backends import BlackMiner
from pyasic.miners.device.models import D10
class HammerD10(BlackMiner, D10):
pass

View File

@@ -0,0 +1 @@
from .D10 import HammerD10

View File

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

View File

@@ -0,0 +1,14 @@
from pyasic.miners.backends.iceriver import IceRiver
from pyasic.miners.device.models.iceriver import KS5, KS5L, KS5M
class IceRiverKS5(IceRiver, KS5):
pass
class IceRiverKS5L(IceRiver, KS5L):
pass
class IceRiverKS5M(IceRiver, KS5M):
pass

View File

@@ -2,3 +2,4 @@ from .KS0 import IceRiverKS0
from .KS1 import IceRiverKS1 from .KS1 import IceRiverKS1
from .KS2 import IceRiverKS2 from .KS2 import IceRiverKS2
from .KS3 import IceRiverKS3, IceRiverKS3L, IceRiverKS3M from .KS3 import IceRiverKS3, IceRiverKS3L, IceRiverKS3M
from .KS5 import IceRiverKS5, IceRiverKS5L, IceRiverKS5M

View File

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

View File

@@ -1 +1,2 @@
from .A11 import InnosiliconA11
from .A11M import InnosiliconA11MX from .A11M import InnosiliconA11MX

View File

@@ -26,6 +26,7 @@ from pyasic.miners.device.models import (
M50VH60, M50VH60,
M50VH70, M50VH70,
M50VH80, M50VH80,
M50VH90,
M50VJ10, M50VJ10,
M50VJ20, M50VJ20,
M50VJ30, M50VJ30,
@@ -72,6 +73,10 @@ class BTMinerM50VH80(M5X, M50VH80):
pass pass
class BTMinerM50VH90(M5X, M50VH90):
pass
class BTMinerM50VJ10(M5X, M50VJ10): class BTMinerM50VJ10(M5X, M50VJ10):
pass pass

View File

@@ -25,6 +25,7 @@ from .M50 import (
BTMinerM50VH60, BTMinerM50VH60,
BTMinerM50VH70, BTMinerM50VH70,
BTMinerM50VH80, BTMinerM50VH80,
BTMinerM50VH90,
BTMinerM50VJ10, BTMinerM50VJ10,
BTMinerM50VJ20, BTMinerM50VJ20,
BTMinerM50VJ30, BTMinerM50VJ30,

View File

@@ -17,6 +17,7 @@ from .bfgminer import BFGMinerRPCAPI
from .bmminer import BMMinerRPCAPI from .bmminer import BMMinerRPCAPI
from .bosminer import BOSMinerRPCAPI from .bosminer import BOSMinerRPCAPI
from .btminer import BTMinerRPCAPI from .btminer import BTMinerRPCAPI
from .ccminer import CCMinerRPCAPI
from .cgminer import CGMinerRPCAPI from .cgminer import CGMinerRPCAPI
from .gcminer import GCMinerRPCAPI from .gcminer import GCMinerRPCAPI
from .luxminer import LUXMinerRPCAPI from .luxminer import LUXMinerRPCAPI

View File

@@ -22,8 +22,8 @@ import hashlib
import json import json
import logging import logging
import re import re
from typing import Literal, Union
import struct import struct
from typing import Literal, Union
import httpx import httpx
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

34
pyasic/rpc/ccminer.py Normal file
View File

@@ -0,0 +1,34 @@
# ------------------------------------------------------------------------------
# Copyright 2024 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.rpc.bmminer import BMMinerRPCAPI
class CCMinerRPCAPI(BMMinerRPCAPI):
"""An abstraction of the CCMiner API.
Each method corresponds to an API command in CCMiner.
This class abstracts use of the CCMiner API, as well as the
methods for sending commands to it. The `self.send_command()`
function handles sending a command to the miner asynchronously, and
as such is the base for many of the functions in this class, which
rely on it to send the command for them.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.port = 8359

View File

@@ -13,8 +13,9 @@
# 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 typing import Literal from typing import Literal, Optional, Union
from pyasic import APIError
from pyasic.rpc.base import BaseMinerRPCAPI from pyasic.rpc.base import BaseMinerRPCAPI
@@ -32,6 +33,48 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
rely on it to send the command for them. rely on it to send the command for them.
""" """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.session_token = None
async def send_privileged_command(
self, command: Union[str, bytes], *args, **kwargs
) -> dict:
if self.session_token is None:
await self.auth()
return await self.send_command(
command,
self.session_token,
*args,
**kwargs,
)
async def send_command(
self,
command: Union[str, bytes],
*args,
**kwargs,
) -> dict:
if kwargs.get("parameters") is not None and len(args) == 0:
return await super().send_command(command, **kwargs)
return await super().send_command(command, parameters=",".join(args), **kwargs)
async def auth(self) -> Optional[str]:
try:
data = await self.session()
if not data["SESSION"][0]["SessionID"] == "":
self.session_token = data["SESSION"][0]["SessionID"]
return self.session_token
except APIError:
pass
try:
data = await self.logon()
self.session_token = data["SESSION"][0]["SessionID"]
return self.session_token
except (LookupError, APIError):
pass
async def addgroup(self, name: str, quota: int) -> dict: async def addgroup(self, name: str, quota: int) -> dict:
"""Add a pool group. """Add a pool group.
<details> <details>
@@ -45,7 +88,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
Confirmation of adding a pool group. Confirmation of adding a pool group.
</details> </details>
""" """
return await self.send_command("addgroup", parameters=f"{name},{quota}") return await self.send_command("addgroup", name, quota)
async def addpool( async def addpool(
self, url: str, user: str, pwd: str = "", group_id: str = None self, url: str, user: str, pwd: str = "", group_id: str = None
@@ -67,7 +110,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
pool_data = [url, user, pwd] pool_data = [url, user, pwd]
if group_id is not None: if group_id is not None:
pool_data.append(group_id) pool_data.append(group_id)
return await self.send_command("addpool", parameters=",".join(pool_data)) return await self.send_command("addpool", *pool_data)
async def asc(self, n: int) -> dict: async def asc(self, n: int) -> dict:
"""Get data for ASC device n. """Get data for ASC device n.
@@ -81,7 +124,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
The data for ASC device n. The data for ASC device n.
</details> </details>
""" """
return await self.send_command("asc", parameters=n) return await self.send_command("asc", n)
async def asccount(self) -> dict: async def asccount(self) -> dict:
"""Get data on the number of ASC devices and their info. """Get data on the number of ASC devices and their info.
@@ -108,7 +151,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
* Access (Y/N) <- you have access to use the command * Access (Y/N) <- you have access to use the command
</details> </details>
""" """
return await self.send_command("check", parameters=command) return await self.send_command("check", command)
async def coin(self) -> dict: async def coin(self) -> dict:
"""Get information on the current coin. """Get information on the current coin.
@@ -137,19 +180,38 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
""" """
return await self.send_command("config") return await self.send_command("config")
async def curtail(self, session_id: str) -> dict: async def curtail(self) -> dict:
"""Put the miner into sleep mode. Requires a session_id from logon. """Put the miner into sleep mode.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
Parameters:
session_id: Session id from the logon command.
Returns: Returns:
A confirmation of putting the miner to sleep. A confirmation of putting the miner to sleep.
</details> </details>
""" """
return await self.send_command("curtail", parameters=session_id) return await self.send_privileged_command("curtail", "sleep")
async def sleep(self) -> dict:
"""Put the miner into sleep mode.
<details>
<summary>Expand</summary>
Returns:
A confirmation of putting the miner to sleep.
</details>
"""
return await self.send_privileged_command("curtail", "sleep")
async def wakeup(self) -> dict:
"""Wake the miner up from sleep mode.
<details>
<summary>Expand</summary>
Returns:
A confirmation of waking the miner up.
</details>
"""
return await self.send_privileged_command("curtail", "wakeup")
async def devdetails(self) -> dict: async def devdetails(self) -> dict:
"""Get data on all devices with their static details. """Get data on all devices with their static details.
@@ -185,7 +247,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
A confirmation of diabling the pool. A confirmation of diabling the pool.
</details> </details>
""" """
return await self.send_command("disablepool", parameters=n) return await self.send_command("disablepool", n)
async def edevs(self) -> dict: async def edevs(self) -> dict:
"""Alias for devs""" """Alias for devs"""
@@ -203,7 +265,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
A confirmation of enabling pool n. A confirmation of enabling pool n.
</details> </details>
""" """
return await self.send_command("enablepool", parameters=n) return await self.send_command("enablepool", n)
async def estats(self) -> dict: async def estats(self) -> dict:
"""Alias for stats""" """Alias for stats"""
@@ -220,13 +282,14 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
""" """
return await self.send_command("fans") return await self.send_command("fans")
async def fanset(self, session_id: str, speed: int, min_fans: int = None) -> dict: async def fanset(
"""Set fan control. Requires a session_id from logon. self, speed: int = None, min_fans: int = None, power_off_speed: int = None
) -> dict:
"""Set fan control.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
Parameters: Parameters:
session_id: Session id from the logon command.
speed: The fan speed to set. Use -1 to set automatically. speed: The fan speed to set. Use -1 to set automatically.
min_fans: The minimum number of fans to use. Optional. min_fans: The minimum number of fans to use. Optional.
@@ -234,10 +297,14 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
A confirmation of setting fan control values. A confirmation of setting fan control values.
</details> </details>
""" """
fanset_data = [str(session_id), str(speed)] fanset_data = []
if speed is not None:
fanset_data.append(f"speed={speed}")
if min_fans is not None: if min_fans is not None:
fanset_data.append(str(min_fans)) fanset_data.append(f"min_fans={min_fans}")
return await self.send_command("fanset", parameters=",".join(fanset_data)) if power_off_speed is not None:
fanset_data.append(f"power_off_speed={power_off_speed}")
return await self.send_privileged_command("fanset", *fanset_data)
async def frequencyget(self, board_n: int, chip_n: int = None) -> dict: async def frequencyget(self, board_n: int, chip_n: int = None) -> dict:
"""Get frequency data for a board and chips. """Get frequency data for a board and chips.
@@ -255,17 +322,14 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
frequencyget_data = [str(board_n)] frequencyget_data = [str(board_n)]
if chip_n is not None: if chip_n is not None:
frequencyget_data.append(str(chip_n)) frequencyget_data.append(str(chip_n))
return await self.send_command( return await self.send_command("frequencyget", *frequencyget_data)
"frequencyget", parameters=",".join(frequencyget_data)
)
async def frequencyset(self, session_id: str, board_n: int, freq: int) -> dict: async def frequencyset(self, board_n: int, freq: int) -> dict:
"""Set frequency. Requires a session_id from logon. """Set frequency.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
Parameters: Parameters:
session_id: Session id from the logon command.
board_n: The board number to set frequency on. board_n: The board number to set frequency on.
freq: The frequency to set. freq: The frequency to set.
@@ -273,26 +337,21 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
A confirmation of setting frequency values. A confirmation of setting frequency values.
</details> </details>
""" """
return await self.send_command( return await self.send_privileged_command("frequencyset", board_n, freq)
"frequencyset", parameters=f"{session_id},{board_n},{freq}"
)
async def frequencystop(self, session_id: str, board_n: int) -> dict: async def frequencystop(self, board_n: int) -> dict:
"""Stop set frequency. Requires a session_id from logon. """Stop set frequency.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
Parameters: Parameters:
session_id: Session id from the logon command.
board_n: The board number to set frequency on. board_n: The board number to set frequency on.
Returns: Returns:
A confirmation of stopping frequencyset value. A confirmation of stopping frequencyset value.
</details> </details>
""" """
return await self.send_command( return await self.send_privileged_command("frequencystop", board_n)
"frequencystop", parameters=f"{session_id},{board_n}"
)
async def groupquota(self, group_n: int, quota: int) -> dict: async def groupquota(self, group_n: int, quota: int) -> dict:
"""Set a group's quota. """Set a group's quota.
@@ -307,7 +366,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
A confirmation of setting quota value. A confirmation of setting quota value.
</details> </details>
""" """
return await self.send_command("groupquota", parameters=f"{group_n},{quota}") return await self.send_command("groupquota", group_n, quota)
async def groups(self) -> dict: async def groups(self) -> dict:
"""Get pool group data. """Get pool group data.
@@ -336,19 +395,14 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
healthchipget_data = [str(board_n)] healthchipget_data = [str(board_n)]
if chip_n is not None: if chip_n is not None:
healthchipget_data.append(str(chip_n)) healthchipget_data.append(str(chip_n))
return await self.send_command( return await self.send_command("healthchipget", *healthchipget_data)
"healthchipget", parameters=",".join(healthchipget_data)
)
async def healthchipset( async def healthchipset(self, board_n: int, chip_n: int = None) -> dict:
self, session_id: str, board_n: int, chip_n: int = None """Select the next chip to have its health checked.
) -> dict:
"""Select the next chip to have its health checked. Requires a session_id from logon.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
Parameters: Parameters:
session_id: Session id from the logon command.
board_n: The board number to next get chip health of. board_n: The board number to next get chip health of.
chip_n: The chip number to next get chip health of. Optional. chip_n: The chip number to next get chip health of. Optional.
@@ -356,12 +410,10 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
Confirmation of selecting the next health check chip. Confirmation of selecting the next health check chip.
</details> </details>
""" """
healthchipset_data = [session_id, str(board_n)] healthchipset_data = [str(board_n)]
if chip_n is not None: if chip_n is not None:
healthchipset_data.append(str(chip_n)) healthchipset_data.append(str(chip_n))
return await self.send_command( return await self.send_privileged_command("healthchipset", *healthchipset_data)
"healthchipset", parameters=",".join(healthchipset_data)
)
async def healthctrl(self) -> dict: async def healthctrl(self) -> dict:
"""Get health check config. """Get health check config.
@@ -374,15 +426,12 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
""" """
return await self.send_command("healthctrl") return await self.send_command("healthctrl")
async def healthctrlset( async def healthctrlset(self, num_readings: int, amplified_factor: float) -> dict:
self, session_id: str, num_readings: int, amplified_factor: float """Set health control config.
) -> dict:
"""Set health control config. Requires a session_id from logon.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
Parameters: Parameters:
session_id: Session id from the logon command.
num_readings: The minimum number of readings for evaluation. num_readings: The minimum number of readings for evaluation.
amplified_factor: Performance factor of the evaluation. amplified_factor: Performance factor of the evaluation.
@@ -390,9 +439,8 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
A confirmation of setting health control config. A confirmation of setting health control config.
</details> </details>
""" """
return await self.send_command( return await self.send_privileged_command(
"healthctrlset", "healthctrlset", num_readings, amplified_factor
parameters=f"{session_id},{num_readings},{amplified_factor}",
) )
async def kill(self) -> dict: async def kill(self) -> dict:
@@ -419,16 +467,14 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
async def ledset( async def ledset(
self, self,
session_id: str,
color: Literal["red"], color: Literal["red"],
state: Literal["on", "off", "blink"], state: Literal["on", "off", "blink"],
) -> dict: ) -> dict:
"""Set led. Requires a session_id from logon. """Set led.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
Parameters: Parameters:
session_id: Session id from the logon command.
color: The color LED to set. Can be "red". color: The color LED to set. Can be "red".
state: The state to set the LED to. Can be "on", "off", or "blink". state: The state to set the LED to. Can be "on", "off", or "blink".
@@ -436,9 +482,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
A confirmation of setting LED. A confirmation of setting LED.
</details> </details>
""" """
return await self.send_command( return await self.send_privileged_command("ledset", color, state)
"ledset", parameters=f"{session_id},{color},{state}"
)
async def limits(self) -> dict: async def limits(self) -> dict:
"""Get max and min values of config parameters. """Get max and min values of config parameters.
@@ -451,8 +495,8 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
""" """
return await self.send_command("limits") return await self.send_command("limits")
async def logoff(self, session_id: str) -> dict: async def logoff(self) -> dict:
"""Log off of a session. Requires a session id from an active session. """Log off of a session.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
@@ -463,7 +507,9 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
Confirmation of logging off a session. Confirmation of logging off a session.
</details> </details>
""" """
return await self.send_command("logoff", parameters=session_id) res = await self.send_privileged_command("logoff")
self.session_token = None
return res
async def logon(self) -> dict: async def logon(self) -> dict:
"""Get or create a session. """Get or create a session.
@@ -510,13 +556,12 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
""" """
return await self.send_command("profiles") return await self.send_command("profiles")
async def profileset(self, session_id: str, board_n: int, profile: str) -> dict: async def profileset(self, board_n: int, profile: str) -> dict:
"""Set active profile for a board. Requires a session_id from logon. """Set active profile for a board.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
Parameters: Parameters:
session_id: Session id from the logon command.
board_n: The board to set the profile on. board_n: The board to set the profile on.
profile: The profile name to use. profile: The profile name to use.
@@ -524,17 +569,14 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
A confirmation of setting the profile on board_n. A confirmation of setting the profile on board_n.
</details> </details>
""" """
return await self.send_command( return await self.send_privileged_command("profileset", board_n, profile)
"profileset", parameters=f"{session_id},{board_n},{profile}"
)
async def reboot(self, session_id: str, board_n: int, delay_s: int = None) -> dict: async def reboot(self, board_n: int, delay_s: int = None) -> dict:
"""Reboot a board. Requires a session_id from logon. """Reboot a board.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
Parameters: Parameters:
session_id: Session id from the logon command.
board_n: The board to reboot. board_n: The board to reboot.
delay_s: The number of seconds to delay until startup. If it is 0, the board will just stop. Optional. delay_s: The number of seconds to delay until startup. If it is 0, the board will just stop. Optional.
@@ -542,24 +584,21 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
A confirmation of rebooting board_n. A confirmation of rebooting board_n.
</details> </details>
""" """
reboot_data = [session_id, str(board_n)] reboot_data = [str(board_n)]
if delay_s is not None: if delay_s is not None:
reboot_data.append(str(delay_s)) reboot_data.append(str(delay_s))
return await self.send_command("reboot", parameters=",".join(reboot_data)) return await self.send_privileged_command("reboot", *reboot_data)
async def rebootdevice(self, session_id: str) -> dict: async def rebootdevice(self) -> dict:
"""Reboot the miner. Requires a session_id from logon. """Reboot the miner.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
Parameters:
session_id: Session id from the logon command.
Returns: Returns:
A confirmation of rebooting the miner. A confirmation of rebooting the miner.
</details> </details>
""" """
return await self.send_command("rebootdevice", parameters=session_id) return await self.send_privileged_command("rebootdevice")
async def removegroup(self, group_id: str) -> dict: async def removegroup(self, group_id: str) -> dict:
"""Remove a pool group. """Remove a pool group.
@@ -575,19 +614,16 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
""" """
return await self.send_command("removegroup", parameters=group_id) return await self.send_command("removegroup", parameters=group_id)
async def resetminer(self, session_id: str) -> dict: async def resetminer(self) -> dict:
"""Restart the mining process. Requires a session_id from logon. """Restart the mining process.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
Parameters:
session_id: Session id from the logon command.
Returns: Returns:
A confirmation of restarting the mining process. A confirmation of restarting the mining process.
</details> </details>
""" """
return await self.send_command("resetminer", parameters=session_id) return await self.send_privileged_command("resetminer")
async def removepool(self, pool_id: int) -> dict: async def removepool(self, pool_id: int) -> dict:
"""Remove a pool. """Remove a pool.
@@ -614,7 +650,9 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
""" """
return await self.send_command("session") return await self.send_command("session")
async def tempctrlset(self, target: int, hot: int, dangerous: int) -> dict: async def tempctrlset(
self, target: int = None, hot: int = None, dangerous: int = None
) -> dict:
"""Set temp control values. """Set temp control values.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
@@ -629,7 +667,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
</details> </details>
""" """
return await self.send_command( return await self.send_command(
"tempctrlset", parameters=f"{target},{hot},{dangerous}" "tempctrlset", target or "", hot or "", dangerous or ""
) )
async def stats(self) -> dict: async def stats(self) -> dict:
@@ -668,7 +706,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
A confirmation of switching to the pool. A confirmation of switching to the pool.
</details> </details>
""" """
return await self.send_command("switchpool", parameters=str(pool_id)) return await self.send_command("switchpool", pool_id)
async def tempctrl(self) -> dict: async def tempctrl(self) -> dict:
"""Get temperature control data. """Get temperature control data.
@@ -716,15 +754,14 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
Board voltage values. Board voltage values.
</details> </details>
""" """
return await self.send_command("frequencyget", parameters=str(board_n)) return await self.send_command("frequencyget", board_n)
async def voltageset(self, session_id: str, board_n: int, voltage: float) -> dict: async def voltageset(self, board_n: int, voltage: float) -> dict:
"""Set voltage values. """Set voltage values.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
Parameters: Parameters:
session_id: Session id from the logon command.
board_n: The board to set the voltage on. board_n: The board to set the voltage on.
voltage: The voltage to use. voltage: The voltage to use.
@@ -732,23 +769,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
A confirmation of setting the voltage. A confirmation of setting the voltage.
</details> </details>
""" """
return await self.send_command( return await self.send_privileged_command("voltageset", board_n, voltage)
"voltageset", parameters=f"{session_id},{board_n},{voltage}"
)
async def wakeup(self, session_id: str) -> dict:
"""Take the miner out of sleep mode. Requires a session_id from logon.
<details>
<summary>Expand</summary>
Parameters:
session_id: Session id from the logon command.
Returns:
A confirmation of resuming mining.
</details>
"""
return await self.send_command("wakeup", parameters=session_id)
async def upgraderun(self): async def upgraderun(self):
""" """
@@ -757,4 +778,4 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
Returns: Returns:
The response from the miner after sending the 'updaterun' command. The response from the miner after sending the 'updaterun' command.
""" """
return await self.send_command("updaterun") return await self.send_command("updaterun")

View File

@@ -31,6 +31,7 @@ _settings = { # defaults
"default_whatsminer_rpc_password": "admin", "default_whatsminer_rpc_password": "admin",
"default_innosilicon_web_password": "admin", "default_innosilicon_web_password": "admin",
"default_antminer_web_password": "root", "default_antminer_web_password": "root",
"default_hammer_web_password": "root",
"default_bosminer_web_password": "root", "default_bosminer_web_password": "root",
"default_vnish_web_password": "admin", "default_vnish_web_password": "admin",
"default_goldshell_web_password": "123456789", "default_goldshell_web_password": "123456789",

View File

@@ -19,6 +19,7 @@ from .base import BaseWebAPI
from .braiins_os import BOSerWebAPI, BOSMinerWebAPI from .braiins_os import BOSerWebAPI, BOSMinerWebAPI
from .epic import ePICWebAPI from .epic import ePICWebAPI
from .goldshell import GoldshellWebAPI from .goldshell import GoldshellWebAPI
from .hammer import HammerWebAPI
from .iceriver import IceRiverWebAPI from .iceriver import IceRiverWebAPI
from .innosilicon import InnosiliconWebAPI from .innosilicon import InnosiliconWebAPI
from .vnish import VNishWebAPI from .vnish import VNishWebAPI

View File

@@ -17,10 +17,11 @@ from __future__ import annotations
import asyncio import asyncio
import json import json
from pathlib import Path
from typing import Any from typing import Any
import aiofiles import aiofiles
import httpx import httpx
from pathlib import Path
from pyasic import settings from pyasic import settings
from pyasic.web.base import BaseWebAPI from pyasic.web.base import BaseWebAPI
@@ -414,10 +415,7 @@ class AntminerOldWebAPI(BaseWebAPI):
parameters = { parameters = {
"file": (file.name, file_content, "application/octet-stream"), "file": (file.name, file_content, "application/octet-stream"),
"filename": file.name, "filename": file.name,
"keep_settings": keep_settings "keep_settings": keep_settings,
} }
return await self.send_command( return await self.send_command(command="upgrade", **parameters)
command="upgrade",
**parameters
)

View File

@@ -4,17 +4,12 @@
# This file has been @generated # This file has been @generated
from dataclasses import dataclass from dataclasses import dataclass
from typing import ( from typing import TYPE_CHECKING, Dict, Optional
TYPE_CHECKING,
Dict,
Optional,
)
import betterproto import betterproto
import grpclib import grpclib
from betterproto.grpc.grpclib_server import ServiceBase from betterproto.grpc.grpclib_server import ServiceBase
if TYPE_CHECKING: if TYPE_CHECKING:
import grpclib.server import grpclib.server
from betterproto.grpc.grpclib_client import MetadataLike from betterproto.grpc.grpclib_client import MetadataLike

View File

@@ -5,19 +5,12 @@
import warnings import warnings
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from typing import ( from typing import TYPE_CHECKING, AsyncIterator, Dict, List, Optional
TYPE_CHECKING,
AsyncIterator,
Dict,
List,
Optional,
)
import betterproto import betterproto
import grpclib import grpclib
from betterproto.grpc.grpclib_server import ServiceBase from betterproto.grpc.grpclib_server import ServiceBase
if TYPE_CHECKING: if TYPE_CHECKING:
import grpclib.server import grpclib.server
from betterproto.grpc.grpclib_client import MetadataLike from betterproto.grpc.grpclib_client import MetadataLike

240
pyasic/web/hammer.py Normal file
View File

@@ -0,0 +1,240 @@
# ------------------------------------------------------------------------------
# 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 json
from typing import Any
import httpx
from pyasic import settings
from pyasic.web.base import BaseWebAPI
class HammerWebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None:
"""Initialize the modern Hammer API client with a specific IP address.
Args:
ip (str): IP address of the Hammer device.
"""
super().__init__(ip)
self.username = "root"
self.pwd = settings.get("default_hammer_web_password", "root")
async def send_command(
self,
command: str | bytes,
ignore_errors: bool = False,
allow_warning: bool = True,
privileged: bool = False,
**parameters: Any,
) -> dict:
"""Send a command to the Hammer 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.
privileged (bool): If set to True, requires elevated privileges.
**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.
"""
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
auth = httpx.DigestAuth(self.username, self.pwd)
try:
async with httpx.AsyncClient(transport=settings.transport()) as client:
if parameters:
data = await client.post(
url,
auth=auth,
timeout=settings.get("api_function_timeout", 3),
json=parameters,
)
else:
data = await client.get(url, auth=auth)
except httpx.HTTPError as e:
return {"success": False, "message": f"HTTP error occurred: {str(e)}"}
else:
if data.status_code == 200:
try:
return data.json()
except json.decoder.JSONDecodeError:
return {"success": False, "message": "Failed to decode JSON"}
return {"success": False, "message": "Unknown error occurred"}
async def multicommand(
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
) -> dict:
"""Execute multiple commands simultaneously.
Args:
*commands (str): Multiple command strings to be executed.
ignore_errors (bool): If True, ignore any HTTP errors.
allow_warning (bool): If True, proceed with warnings.
Returns:
dict: A dictionary containing the results of all commands executed.
"""
async with httpx.AsyncClient(transport=settings.transport()) as client:
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:
"""Helper function for handling individual commands in a multicommand execution.
Args:
client (httpx.AsyncClient): The HTTP client to use for the request.
command (str): The command to be executed.
Returns:
dict: A dictionary containing the response of the executed command.
"""
auth = httpx.DigestAuth(self.username, self.pwd)
try:
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
ret = await client.get(url, auth=auth)
except httpx.HTTPError:
pass
else:
if ret.status_code == 200:
try:
json_data = ret.json()
return {command: json_data}
except json.decoder.JSONDecodeError:
pass
return {command: {}}
async def get_miner_conf(self) -> dict:
"""Retrieve the miner configuration from the Hammer device.
Returns:
dict: A dictionary containing the current configuration of the miner.
"""
return await self.send_command("get_miner_conf")
async def set_miner_conf(self, conf: dict) -> dict:
"""Set the configuration for the miner.
Args:
conf (dict): A dictionary of configuration settings to apply to the miner.
Returns:
dict: A dictionary response from the device after setting the configuration.
"""
return await self.send_command("set_miner_conf", **conf)
async def blink(self, blink: bool) -> dict:
"""Control the blinking of the LED on the miner device.
Args:
blink (bool): True to start blinking, False to stop.
Returns:
dict: A dictionary response from the device after the command execution.
"""
if blink:
return await self.send_command("blink", blink="true")
return await self.send_command("blink", blink="false")
async def reboot(self) -> dict:
"""Reboot the miner device.
Returns:
dict: A dictionary response from the device confirming the reboot command.
"""
return await self.send_command("reboot")
async def get_system_info(self) -> dict:
"""Retrieve system information from the miner.
Returns:
dict: A dictionary containing system information of the miner.
"""
return await self.send_command("get_system_info")
async def get_network_info(self) -> dict:
"""Retrieve network configuration information from the miner.
Returns:
dict: A dictionary containing the network configuration of the miner.
"""
return await self.send_command("get_network_info")
async def summary(self) -> dict:
"""Get a summary of the miner's status and performance.
Returns:
dict: A summary of the miner's current operational status.
"""
return await self.send_command("summary")
async def get_blink_status(self) -> dict:
"""Check the status of the LED blinking on the miner.
Returns:
dict: A dictionary indicating whether the LED is currently blinking.
"""
return await self.send_command("get_blink_status")
async def set_network_conf(
self,
ip: str,
dns: str,
gateway: str,
subnet_mask: str,
hostname: str,
protocol: int,
) -> dict:
"""Set the network configuration of the miner.
Args:
ip (str): IP address of the device.
dns (str): DNS server IP address.
gateway (str): Gateway IP address.
subnet_mask (str): Network subnet mask.
hostname (str): Hostname of the device.
protocol (int): Network protocol used.
Returns:
dict: A dictionary response from the device after setting the network configuration.
"""
return await self.send_command(
"set_network_conf",
ipAddress=ip,
ipDns=dns,
ipGateway=gateway,
ipHost=hostname,
ipPro=protocol,
ipSub=subnet_mask,
)

View File

@@ -127,6 +127,9 @@ class InnosiliconWebAPI(BaseWebAPI):
async def get_all(self) -> dict: async def get_all(self) -> dict:
return await self.send_command("getAll") return await self.send_command("getAll")
async def summary(self) -> dict:
return await self.send_command("summary")
async def get_error_detail(self) -> dict: async def get_error_detail(self) -> dict:
return await self.send_command("getErrorDetail") return await self.send_command("getErrorDetail")

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "pyasic" name = "pyasic"
version = "0.61.4" version = "0.62.4"
description = "A simplified and standardized interface for Bitcoin ASICs." description = "A simplified and standardized interface for Bitcoin ASICs."
authors = ["UpstreamData <brett@upstreamdata.ca>"] authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic" repository = "https://github.com/UpstreamData/pyasic"
@@ -15,8 +15,8 @@ passlib = ">=1.7.4"
pyaml = ">=23.12.0" pyaml = ">=23.12.0"
tomli = { version = ">=2.0.1", python = "<3.11" } tomli = { version = ">=2.0.1", python = "<3.11" }
tomli-w = "^1.0.0" tomli-w = "^1.0.0"
betterproto = "2.0.0b7"
aiofiles = ">=23.2.1" aiofiles = ">=23.2.1"
betterproto = "2.0.0b7"
[tool.poetry.group.dev] [tool.poetry.group.dev]
optional = true optional = true