Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccb5eb73db | ||
|
|
d143667bd6 | ||
|
|
87d809abc0 | ||
|
|
4dc5b1a541 | ||
|
|
ddd3e867f9 | ||
|
|
77480d3d69 | ||
|
|
0767c93002 | ||
|
|
e690e6dd3b | ||
|
|
d4665ed768 | ||
|
|
b90a92c0df | ||
|
|
50cfcf9796 | ||
|
|
5d204f09da | ||
|
|
4c0410322f | ||
|
|
fbb2b3f6e7 | ||
|
|
0f09fb49fc | ||
|
|
b0d063d6ed | ||
|
|
a68fe70af4 | ||
|
|
43c7ac281b | ||
|
|
a97ae55a06 | ||
|
|
4a3a6f4186 | ||
|
|
f976724ada | ||
|
|
2632bdaa30 | ||
|
|
91016d7b8c | ||
|
|
2b00e741ca | ||
|
|
d496c11d67 | ||
|
|
5880223517 | ||
|
|
394a5dcd0d | ||
|
|
7365275f46 | ||
|
|
0ecab5fdd4 | ||
|
|
ed0d9f73e4 | ||
|
|
28f4e16662 | ||
|
|
b9b0bff946 | ||
|
|
790718a5df | ||
|
|
96a0301f5e | ||
|
|
c57b019b7d | ||
|
|
af920c4dda | ||
|
|
f3d11788ed | ||
|
|
fd0e02af59 | ||
|
|
2a6c51d52c | ||
|
|
2d62e2070b | ||
|
|
b143bd70f0 | ||
|
|
605509c57c | ||
|
|
7036137b23 | ||
|
|
7a9ff535b4 | ||
|
|
f185bafe2a | ||
|
|
ab81d5d020 | ||
|
|
0965e6489b | ||
|
|
792e1c9cad | ||
|
|
a6721f971a | ||
|
|
8113d0e4e0 | ||
|
|
e3c7d3f8a2 | ||
|
|
6415de8c73 | ||
|
|
f2838cf31d | ||
|
|
fbd49b370d | ||
|
|
79f7296576 | ||
|
|
76f4ca5f89 | ||
|
|
477acda1c1 | ||
|
|
a57f343dcc | ||
|
|
36e9201ed4 | ||
|
|
c1525501d4 | ||
|
|
e4bb90a569 | ||
|
|
28642cc521 | ||
|
|
beae79ddec | ||
|
|
f02e10ab3d | ||
|
|
21636a75fa | ||
|
|
6fdd156fa3 | ||
|
|
e9fcf25ad3 | ||
|
|
0ea5ee8239 | ||
|
|
fba25cba61 | ||
|
|
343b5a1c50 | ||
|
|
b957aa7fba | ||
|
|
a71aa6868a | ||
|
|
6b50bf0cf7 | ||
|
|
d00444ec56 |
@@ -51,6 +51,8 @@ def backend_str(backend: MinerTypes) -> str:
|
|||||||
return "Mara Firmware Miners"
|
return "Mara Firmware Miners"
|
||||||
case MinerTypes.BITAXE:
|
case MinerTypes.BITAXE:
|
||||||
return "Stock Firmware BitAxe Miners"
|
return "Stock Firmware BitAxe Miners"
|
||||||
|
case MinerTypes.ICERIVER:
|
||||||
|
return "Stock Firmware IceRiver Miners"
|
||||||
|
|
||||||
|
|
||||||
def create_url_str(mtype: str):
|
def create_url_str(mtype: str):
|
||||||
|
|||||||
@@ -225,6 +225,13 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19 Pro+ Hydro (BOS+)
|
||||||
|
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19ProPlusHydro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## T19 (BOS+)
|
## T19 (BOS+)
|
||||||
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
|
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
|
||||||
handler: python
|
handler: python
|
||||||
@@ -281,6 +288,13 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19 Pro Hydro (VNish)
|
||||||
|
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19ProHydro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## T19 (VNish)
|
## T19 (VNish)
|
||||||
::: pyasic.miners.antminer.vnish.X19.T19.VNishT19
|
::: pyasic.miners.antminer.vnish.X19.T19.VNishT19
|
||||||
handler: python
|
handler: python
|
||||||
|
|||||||
@@ -8,6 +8,13 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## S21 Pro (Stock)
|
||||||
|
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Pro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## T21 (Stock)
|
## T21 (Stock)
|
||||||
::: pyasic.miners.antminer.bmminer.X21.T21.BMMinerT21
|
::: pyasic.miners.antminer.bmminer.X21.T21.BMMinerT21
|
||||||
handler: python
|
handler: python
|
||||||
@@ -36,6 +43,13 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## S21 Pro (ePIC)
|
||||||
|
::: pyasic.miners.antminer.epic.X21.S21.ePICS21Pro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## T21 (ePIC)
|
## T21 (ePIC)
|
||||||
::: pyasic.miners.antminer.epic.X21.T21.ePICT21
|
::: pyasic.miners.antminer.epic.X21.T21.ePICT21
|
||||||
handler: python
|
handler: python
|
||||||
|
|||||||
10
docs/miners/iceriver/KSX.md
Normal file
10
docs/miners/iceriver/KSX.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# pyasic
|
||||||
|
## KSX Models
|
||||||
|
|
||||||
|
## KS2 (Stock)
|
||||||
|
::: pyasic.miners.iceriver.iceminer.KSX.KS2.IceRiverKS2
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
@@ -89,6 +89,7 @@ details {
|
|||||||
<summary>X21 Series:</summary>
|
<summary>X21 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
|
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21-pro-stock">S21 Pro (Stock)</a></li>
|
||||||
<li><a href="../antminer/X21#t21-stock">T21 (Stock)</a></li>
|
<li><a href="../antminer/X21#t21-stock">T21 (Stock)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -461,6 +462,7 @@ details {
|
|||||||
<li><a href="../antminer/X19#s19k-pro-no-pic-bos_1">S19k Pro No PIC (BOS+)</a></li>
|
<li><a href="../antminer/X19#s19k-pro-no-pic-bos_1">S19k Pro No PIC (BOS+)</a></li>
|
||||||
<li><a href="../antminer/X19#s19k-pro-no-pic-bos_1">S19k Pro No PIC (BOS+)</a></li>
|
<li><a href="../antminer/X19#s19k-pro-no-pic-bos_1">S19k Pro No PIC (BOS+)</a></li>
|
||||||
<li><a href="../antminer/X19#s19-xp-bos_1">S19 XP (BOS+)</a></li>
|
<li><a href="../antminer/X19#s19-xp-bos_1">S19 XP (BOS+)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19-pro_1-hydro-bos_1">S19 Pro+ Hydro (BOS+)</a></li>
|
||||||
<li><a href="../antminer/X19#t19-bos_1">T19 (BOS+)</a></li>
|
<li><a href="../antminer/X19#t19-bos_1">T19 (BOS+)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -505,6 +507,7 @@ details {
|
|||||||
<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#t19-vnish">T19 (VNish)</a></li>
|
<li><a href="../antminer/X19#t19-vnish">T19 (VNish)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -535,6 +538,7 @@ details {
|
|||||||
<summary>X21 Series:</summary>
|
<summary>X21 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="../antminer/X21#s21-epic">S21 (ePIC)</a></li>
|
<li><a href="../antminer/X21#s21-epic">S21 (ePIC)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21-pro-epic">S21 Pro (ePIC)</a></li>
|
||||||
<li><a href="../antminer/X21#t21-epic">T21 (ePIC)</a></li>
|
<li><a href="../antminer/X21#t21-epic">T21 (ePIC)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -650,4 +654,15 @@ details {
|
|||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>Stock Firmware IceRiver Miners:</summary>
|
||||||
|
<ul>
|
||||||
|
<details>
|
||||||
|
<summary>KSX Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../iceriver/KSX#ks2-stock">KS2 (Stock)</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
806
poetry.lock
generated
806
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -198,7 +198,7 @@ class MiningModePowerTune(MinerConfigValue):
|
|||||||
def as_boser(self) -> dict:
|
def as_boser(self) -> dict:
|
||||||
cfg = {
|
cfg = {
|
||||||
"set_performance_mode": SetPerformanceModeRequest(
|
"set_performance_mode": SetPerformanceModeRequest(
|
||||||
save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action=SaveAction.SAVE_AND_APPLY,
|
||||||
mode=PerformanceMode(
|
mode=PerformanceMode(
|
||||||
tuner_mode=TunerPerformanceMode(
|
tuner_mode=TunerPerformanceMode(
|
||||||
power_target=PowerTargetMode(
|
power_target=PowerTargetMode(
|
||||||
@@ -275,7 +275,7 @@ class MiningModeHashrateTune(MinerConfigValue):
|
|||||||
def as_boser(self) -> dict:
|
def as_boser(self) -> dict:
|
||||||
cfg = {
|
cfg = {
|
||||||
"set_performance_mode": SetPerformanceModeRequest(
|
"set_performance_mode": SetPerformanceModeRequest(
|
||||||
save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action=SaveAction.SAVE_AND_APPLY,
|
||||||
mode=PerformanceMode(
|
mode=PerformanceMode(
|
||||||
tuner_mode=TunerPerformanceMode(
|
tuner_mode=TunerPerformanceMode(
|
||||||
hashrate_target=HashrateTargetMode(
|
hashrate_target=HashrateTargetMode(
|
||||||
|
|||||||
@@ -467,7 +467,7 @@ class PoolConfig(MinerConfigValue):
|
|||||||
def as_boser(self, user_suffix: str = None) -> dict:
|
def as_boser(self, user_suffix: str = None) -> dict:
|
||||||
return {
|
return {
|
||||||
"set_pool_groups": SetPoolGroupsRequest(
|
"set_pool_groups": SetPoolGroupsRequest(
|
||||||
save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action=SaveAction.SAVE_AND_APPLY,
|
||||||
pool_groups=[g.as_boser(user_suffix=user_suffix) for g in self.groups],
|
pool_groups=[g.as_boser(user_suffix=user_suffix) for g in self.groups],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from urllib.parse import urlparse
|
|||||||
class Scheme(Enum):
|
class Scheme(Enum):
|
||||||
STRATUM_V1 = "stratum+tcp"
|
STRATUM_V1 = "stratum+tcp"
|
||||||
STRATUM_V2 = "stratum2+tcp"
|
STRATUM_V2 = "stratum2+tcp"
|
||||||
|
STRATUM_V1_SSL = "stratum+ssl"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -25,7 +26,10 @@ class PoolUrl:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def from_str(cls, url: str) -> "PoolUrl":
|
def from_str(cls, url: str) -> "PoolUrl":
|
||||||
parsed_url = urlparse(url)
|
parsed_url = urlparse(url)
|
||||||
scheme = Scheme(parsed_url.scheme)
|
if not parsed_url.scheme.strip() == "":
|
||||||
|
scheme = Scheme(parsed_url.scheme)
|
||||||
|
else:
|
||||||
|
scheme = Scheme.STRATUM_V1
|
||||||
host = parsed_url.hostname
|
host = parsed_url.hostname
|
||||||
port = parsed_url.port
|
port = parsed_url.port
|
||||||
pubkey = parsed_url.path.lstrip("/") if scheme == Scheme.STRATUM_V2 else None
|
pubkey = parsed_url.path.lstrip("/") if scheme == Scheme.STRATUM_V2 else None
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ class AntminerModels(str, Enum):
|
|||||||
D3 = "D3"
|
D3 = "D3"
|
||||||
HS3 = "HS3"
|
HS3 = "HS3"
|
||||||
L3Plus = "L3+"
|
L3Plus = "L3+"
|
||||||
|
KA3 = "KA3"
|
||||||
|
KS3 = "KS3"
|
||||||
DR5 = "DR5"
|
DR5 = "DR5"
|
||||||
|
KS5 = "KS5"
|
||||||
L7 = "L7"
|
L7 = "L7"
|
||||||
E9Pro = "E9Pro"
|
E9Pro = "E9Pro"
|
||||||
S9 = "S9"
|
S9 = "S9"
|
||||||
@@ -284,6 +287,7 @@ class AvalonminerModels(str, Enum):
|
|||||||
Avalon1066 = "Avalon 1066"
|
Avalon1066 = "Avalon 1066"
|
||||||
Avalon1166Pro = "Avalon 1166 Pro"
|
Avalon1166Pro = "Avalon 1166 Pro"
|
||||||
Avalon1246 = "Avalon 1246"
|
Avalon1246 = "Avalon 1246"
|
||||||
|
AvalonNano3 = "Avalon Nano 3"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
@@ -292,6 +296,7 @@ class AvalonminerModels(str, Enum):
|
|||||||
class InnosiliconModels(str, Enum):
|
class InnosiliconModels(str, Enum):
|
||||||
T3HPlus = "T3H+"
|
T3HPlus = "T3H+"
|
||||||
A10X = "A10X"
|
A10X = "A10X"
|
||||||
|
A11MX = "A11MX"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
@@ -339,6 +344,13 @@ class BitAxeModels(str, Enum):
|
|||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
class IceRiverModels(str, Enum):
|
||||||
|
KS2 = "KS2"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class MinerModel:
|
class MinerModel:
|
||||||
ANTMINER = AntminerModels
|
ANTMINER = AntminerModels
|
||||||
WHATSMINER = WhatsminerModels
|
WHATSMINER = WhatsminerModels
|
||||||
@@ -348,3 +360,4 @@ class MinerModel:
|
|||||||
AURADINE = AuradineModels
|
AURADINE = AuradineModels
|
||||||
EPIC = ePICModels
|
EPIC = ePICModels
|
||||||
BITAXE = BitAxeModels
|
BITAXE = BitAxeModels
|
||||||
|
ICERIVER = IceRiverModels
|
||||||
|
|||||||
22
pyasic/miners/antminer/bmminer/X3/KA3.py
Normal file
22
pyasic/miners/antminer/bmminer/X3/KA3.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from pyasic.miners.backends import AntminerModern
|
||||||
|
from pyasic.miners.device.models import KA3
|
||||||
|
|
||||||
|
|
||||||
|
class BMMinerKA3(AntminerModern, KA3):
|
||||||
|
pass
|
||||||
22
pyasic/miners/antminer/bmminer/X3/KS3.py
Normal file
22
pyasic/miners/antminer/bmminer/X3/KS3.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from pyasic.miners.backends import AntminerModern
|
||||||
|
from pyasic.miners.device.models import KS3
|
||||||
|
|
||||||
|
|
||||||
|
class BMMinerKS3(AntminerModern, KS3):
|
||||||
|
pass
|
||||||
@@ -14,4 +14,6 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from .HS3 import BMMinerHS3
|
from .HS3 import BMMinerHS3
|
||||||
|
from .KA3 import BMMinerKA3
|
||||||
|
from .KS3 import BMMinerKS3
|
||||||
from .L3 import BMMinerL3Plus
|
from .L3 import BMMinerL3Plus
|
||||||
|
|||||||
21
pyasic/miners/antminer/bmminer/X5/KS5.py
Normal file
21
pyasic/miners/antminer/bmminer/X5/KS5.py
Normal 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 KS5
|
||||||
|
|
||||||
|
|
||||||
|
class BMMinerKS5(AntminerModern, KS5):
|
||||||
|
supports_shutdown = False
|
||||||
16
pyasic/miners/antminer/bmminer/X5/__init__.py
Normal file
16
pyasic/miners/antminer/bmminer/X5/__init__.py
Normal 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 .KS5 import BMMinerKS5
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from .X3 import *
|
from .X3 import *
|
||||||
|
from .X5 import *
|
||||||
from .X7 import *
|
from .X7 import *
|
||||||
from .X9 import *
|
from .X9 import *
|
||||||
from .X17 import *
|
from .X17 import *
|
||||||
|
|||||||
22
pyasic/miners/antminer/bosminer/X21/T21.py
Normal file
22
pyasic/miners/antminer/bosminer/X21/T21.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from pyasic.miners.backends import BOSer
|
||||||
|
from pyasic.miners.device.models import T21
|
||||||
|
|
||||||
|
|
||||||
|
class BOSMinerT21(BOSer, T21):
|
||||||
|
pass
|
||||||
@@ -15,3 +15,4 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from .S21 import BOSMinerS21
|
from .S21 import BOSMinerS21
|
||||||
|
from .T21 import BOSMinerT21
|
||||||
|
|||||||
@@ -20,3 +20,4 @@ from .A9X import *
|
|||||||
from .A10X import *
|
from .A10X import *
|
||||||
from .A11X import *
|
from .A11X import *
|
||||||
from .A12X import *
|
from .A12X import *
|
||||||
|
from .nano import *
|
||||||
|
|||||||
17
pyasic/miners/avalonminer/cgminer/nano/__init__.py
Normal file
17
pyasic/miners/avalonminer/cgminer/nano/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from .nano3 import CGMinerAvalonNano3
|
||||||
22
pyasic/miners/avalonminer/cgminer/nano/nano3.py
Normal file
22
pyasic/miners/avalonminer/cgminer/nano/nano3.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from pyasic.miners.backends import AvalonMiner
|
||||||
|
from pyasic.miners.device.models import AvalonNano3
|
||||||
|
|
||||||
|
|
||||||
|
class CGMinerAvalonNano3(AvalonMiner, AvalonNano3):
|
||||||
|
pass
|
||||||
@@ -24,6 +24,7 @@ from .cgminer import CGMiner
|
|||||||
from .epic import ePIC
|
from .epic import ePIC
|
||||||
from .goldshell import GoldshellMiner
|
from .goldshell import GoldshellMiner
|
||||||
from .hiveon import Hiveon
|
from .hiveon import Hiveon
|
||||||
|
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
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
from typing import List, Optional, Union
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
from pyasic.config import MinerConfig, MiningModeConfig
|
from pyasic.config import MinerConfig, MiningModeConfig
|
||||||
@@ -124,6 +126,41 @@ class AntminerModern(BMMiner):
|
|||||||
# break
|
# break
|
||||||
# await asyncio.sleep(1)
|
# await asyncio.sleep(1)
|
||||||
|
|
||||||
|
async def upgrade_firmware(self, file: Path, keep_settings: bool = True) -> str:
|
||||||
|
"""
|
||||||
|
Upgrade the firmware of the AntMiner device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file (Path): Path to the firmware file.
|
||||||
|
keep_settings (bool): Whether to keep the current settings after the update.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Result of the upgrade process.
|
||||||
|
"""
|
||||||
|
if not file:
|
||||||
|
raise ValueError("File location must be provided for firmware upgrade.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = await self.web.update_firmware(
|
||||||
|
file=file, keep_settings=keep_settings
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
logging.info(
|
||||||
|
"Firmware upgrade process completed successfully for AntMiner."
|
||||||
|
)
|
||||||
|
return "Firmware upgrade completed successfully."
|
||||||
|
else:
|
||||||
|
error_message = result.get("message", "Unknown error")
|
||||||
|
logging.error(f"Firmware upgrade failed. Response: {error_message}")
|
||||||
|
return f"Firmware upgrade failed. Response: {error_message}"
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(
|
||||||
|
f"An error occurred during the firmware upgrade process: {e}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
data = await self.web.blink(blink=True)
|
data = await self.web.blink(blink=True)
|
||||||
if data:
|
if data:
|
||||||
@@ -337,7 +374,7 @@ class AntminerModern(BMMiner):
|
|||||||
|
|
||||||
if web_get_conf is not None:
|
if web_get_conf is not None:
|
||||||
try:
|
try:
|
||||||
if web_get_conf["bitmain-work-mode"].isdigit():
|
if str(web_get_conf["bitmain-work-mode"]).isdigit():
|
||||||
return (
|
return (
|
||||||
False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
|
False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class AvalonMiner(CGMiner):
|
|||||||
stats_items = []
|
stats_items = []
|
||||||
stats_dict = {}
|
stats_dict = {}
|
||||||
for item in _stats_items:
|
for item in _stats_items:
|
||||||
if ":" in item:
|
if ": " in item:
|
||||||
data = item.replace("]", "").split("[")
|
data = item.replace("]", "").split("[")
|
||||||
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
|
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
|
||||||
data_dict = {}
|
data_dict = {}
|
||||||
@@ -147,10 +147,7 @@ class AvalonMiner(CGMiner):
|
|||||||
if raw_data[0] == "":
|
if raw_data[0] == "":
|
||||||
raw_data = raw_data[1:]
|
raw_data = raw_data[1:]
|
||||||
|
|
||||||
if len(raw_data) == 2:
|
stats_dict[raw_data[0]] = raw_data[1:]
|
||||||
stats_dict[raw_data[0]] = raw_data[1]
|
|
||||||
else:
|
|
||||||
stats_dict[raw_data[0]] = raw_data[1:]
|
|
||||||
stats_items.append(raw_data)
|
stats_items.append(raw_data)
|
||||||
|
|
||||||
return stats_dict
|
return stats_dict
|
||||||
@@ -220,7 +217,7 @@ class AvalonMiner(CGMiner):
|
|||||||
try:
|
try:
|
||||||
board_hr = parsed_stats["MGHS"][board]
|
board_hr = parsed_stats["MGHS"][board]
|
||||||
hashboards[board].hashrate = AlgoHashRate.SHA256(
|
hashboards[board].hashrate = AlgoHashRate.SHA256(
|
||||||
board_hr, HashUnit.SHA256.GH
|
float(board_hr), HashUnit.SHA256.GH
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
@@ -256,7 +253,7 @@ class AvalonMiner(CGMiner):
|
|||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
return AlgoHashRate.SHA256(
|
return AlgoHashRate.SHA256(
|
||||||
parsed_stats["GHSmm"], HashUnit.SHA256.GH
|
float(parsed_stats["GHSmm"][0]), HashUnit.SHA256.GH
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default)
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
@@ -272,7 +269,7 @@ class AvalonMiner(CGMiner):
|
|||||||
try:
|
try:
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
return float(parsed_stats["Temp"])
|
return float(parsed_stats["Temp"][0])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -287,7 +284,7 @@ class AvalonMiner(CGMiner):
|
|||||||
try:
|
try:
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
return int(parsed_stats["MPO"])
|
return int(parsed_stats["MPO"][0])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -308,7 +305,7 @@ class AvalonMiner(CGMiner):
|
|||||||
|
|
||||||
for fan in range(self.expected_fans):
|
for fan in range(self.expected_fans):
|
||||||
try:
|
try:
|
||||||
fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"])
|
fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"][0])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
return fans_data
|
return fans_data
|
||||||
@@ -326,7 +323,7 @@ class AvalonMiner(CGMiner):
|
|||||||
try:
|
try:
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
led = int(parsed_stats["Led"])
|
led = int(parsed_stats["Led"][0])
|
||||||
return True if led == 1 else False
|
return True if led == 1 else False
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ 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(
|
||||||
**{
|
**{
|
||||||
@@ -49,6 +50,10 @@ BFGMINER_DATA_LOC = DataLocations(
|
|||||||
"_get_fans",
|
"_get_fans",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
|
str(DataOptions.POOLS): DataFunction(
|
||||||
|
"_get_pools",
|
||||||
|
[RPCAPICommand("rpc_pools", "pools")],
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -207,6 +212,36 @@ class BFGMiner(StockFirmware):
|
|||||||
|
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, rpc_stats: dict = None
|
self, rpc_stats: dict = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> Optional[AlgoHashRate]:
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ BITAXE_DATA_LOC = DataLocations(
|
|||||||
"_get_api_ver",
|
"_get_api_ver",
|
||||||
[WebAPICommand("web_system_info", "system/info")],
|
[WebAPICommand("web_system_info", "system/info")],
|
||||||
),
|
),
|
||||||
|
str(DataOptions.MAC): DataFunction(
|
||||||
|
"_get_mac",
|
||||||
|
[WebAPICommand("web_system_info", "system/info")],
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -187,3 +191,16 @@ class BitAxe(BaseMiner):
|
|||||||
return web_system_info["version"]
|
return web_system_info["version"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def _get_mac(self, web_system_info: dict = None) -> Optional[str]:
|
||||||
|
if web_system_info is None:
|
||||||
|
try:
|
||||||
|
web_system_info = await self.web.system_info()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_system_info is not None:
|
||||||
|
try:
|
||||||
|
return web_system_info["macAddr"].upper()
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from typing import List, Optional, Union
|
|||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
import tomli_w
|
import tomli_w
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import tomllib
|
import tomllib
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -726,9 +727,8 @@ BOSER_DATA_LOC = DataLocations(
|
|||||||
[RPCAPICommand("rpc_summary", "summary")],
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.POOLS): DataFunction(
|
str(DataOptions.POOLS): DataFunction(
|
||||||
"_get_pools",
|
"_get_pools", [WebAPICommand("grpc_pool_groups", "get_pool_groups")]
|
||||||
[WebAPICommand("grpc_pool_groups", "get_pool_groups")]
|
),
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -798,7 +798,7 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
try:
|
try:
|
||||||
result = await self.web.set_power_target(
|
result = await self.web.set_power_target(
|
||||||
wattage, save_action=SaveAction.SAVE_ACTION_SAVE_AND_FORCE_APPLY
|
wattage, save_action=SaveAction.SAVE_AND_FORCE_APPLY
|
||||||
)
|
)
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
@@ -926,8 +926,10 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if grpc_hashboards is not None:
|
if grpc_hashboards is not None:
|
||||||
for board in grpc_hashboards["hashboards"]:
|
grpc_boards = sorted(
|
||||||
idx = int(board["id"]) - 1
|
grpc_hashboards["hashboards"], key=lambda x: int(x["id"])
|
||||||
|
)
|
||||||
|
for idx, board in enumerate(grpc_boards):
|
||||||
if board.get("chipsCount") is not None:
|
if board.get("chipsCount") is not None:
|
||||||
hashboards[idx].chips = board["chipsCount"]
|
hashboards[idx].chips = board["chipsCount"]
|
||||||
if board.get("boardTemp") is not None:
|
if board.get("boardTemp") is not None:
|
||||||
@@ -951,7 +953,7 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
|
async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
|
||||||
if grpc_miner_stats is None:
|
if grpc_miner_stats is None:
|
||||||
try:
|
try:
|
||||||
grpc_miner_stats = self.web.get_miner_stats()
|
grpc_miner_stats = await self.web.get_miner_stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -983,7 +985,7 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]:
|
async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]:
|
||||||
if grpc_cooling_state is None:
|
if grpc_cooling_state is None:
|
||||||
try:
|
try:
|
||||||
grpc_cooling_state = self.web.get_cooling_state()
|
grpc_cooling_state = await self.web.get_cooling_state()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -1086,12 +1088,12 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
url=pool_info["url"],
|
url=pool_info["url"],
|
||||||
user=pool_info["user"],
|
user=pool_info["user"],
|
||||||
index=idx,
|
index=idx,
|
||||||
accepted=pool_info["stats"]["acceptedShares"],
|
accepted=pool_info["stats"].get("acceptedShares", 0),
|
||||||
rejected=pool_info["stats"]["rejectedShares"],
|
rejected=pool_info["stats"].get("rejectedShares", 0),
|
||||||
get_failures=pool_info["stats"]["stale_shares"],
|
get_failures=0,
|
||||||
remote_failures=0,
|
remote_failures=0,
|
||||||
active=pool_info["active"],
|
active=pool_info.get("active", False),
|
||||||
alive=pool_info["alive"]
|
alive=pool_info.get("alive"),
|
||||||
)
|
)
|
||||||
pools_data.append(pool_data)
|
pools_data.append(pool_data)
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ 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(
|
||||||
**{
|
**{
|
||||||
@@ -109,6 +110,10 @@ BTMINER_DATA_LOC = DataLocations(
|
|||||||
"_get_uptime",
|
"_get_uptime",
|
||||||
[RPCAPICommand("rpc_summary", "summary")],
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
|
str(DataOptions.POOLS): DataFunction(
|
||||||
|
"_get_pools",
|
||||||
|
[RPCAPICommand("rpc_pools", "pools")],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -655,13 +660,42 @@ class BTMiner(StockFirmware):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def upgrade_firmware(self, file: Path, token: str):
|
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
|
||||||
|
|
||||||
|
async def upgrade_firmware(self, file: Path):
|
||||||
"""
|
"""
|
||||||
Upgrade the firmware of the Whatsminer device.
|
Upgrade the firmware of the Whatsminer device.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file (Path): The local file path of the firmware to be uploaded.
|
file (Path): The local file path of the firmware to be uploaded.
|
||||||
token (str): The authentication token for the firmware upgrade.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Confirmation message after upgrading the firmware.
|
str: Confirmation message after upgrading the firmware.
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ GOLDSHELL_DATA_LOC = DataLocations(
|
|||||||
"_get_fans",
|
"_get_fans",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
|
str(DataOptions.POOLS): DataFunction(
|
||||||
|
"_get_pools",
|
||||||
|
[RPCAPICommand("rpc_pools", "pools")],
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
198
pyasic/miners/backends/iceriver.py
Normal file
198
pyasic/miners/backends/iceriver.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
|
||||||
|
from pyasic.device import MinerAlgo
|
||||||
|
from pyasic.errors import APIError
|
||||||
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||||
|
from pyasic.miners.device.firmware import StockFirmware
|
||||||
|
from pyasic.web.iceriver import IceRiverWebAPI
|
||||||
|
|
||||||
|
ICERIVER_DATA_LOC = DataLocations(
|
||||||
|
**{
|
||||||
|
str(DataOptions.MAC): DataFunction(
|
||||||
|
"_get_mac",
|
||||||
|
[WebAPICommand("web_userpanel", "userpanel")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FANS): DataFunction(
|
||||||
|
"_get_fans",
|
||||||
|
[WebAPICommand("web_userpanel", "userpanel")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HOSTNAME): DataFunction(
|
||||||
|
"_get_hostname",
|
||||||
|
[WebAPICommand("web_userpanel", "userpanel")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
|
"_get_hashrate",
|
||||||
|
[WebAPICommand("web_userpanel", "userpanel")],
|
||||||
|
),
|
||||||
|
str(DataOptions.IS_MINING): DataFunction(
|
||||||
|
"_is_mining",
|
||||||
|
[WebAPICommand("web_userpanel", "userpanel")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
|
"_get_fault_light",
|
||||||
|
[WebAPICommand("web_userpanel", "userpanel")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
|
"_get_hashboards",
|
||||||
|
[WebAPICommand("web_userpanel", "userpanel")],
|
||||||
|
),
|
||||||
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
|
"_get_uptime",
|
||||||
|
[WebAPICommand("web_userpanel", "userpanel")],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IceRiver(StockFirmware):
|
||||||
|
"""Handler for IceRiver miners"""
|
||||||
|
|
||||||
|
_web_cls = IceRiverWebAPI
|
||||||
|
web: IceRiverWebAPI
|
||||||
|
|
||||||
|
data_locations = ICERIVER_DATA_LOC
|
||||||
|
|
||||||
|
async def fault_light_off(self) -> bool:
|
||||||
|
try:
|
||||||
|
await self.web.locate(False)
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def fault_light_on(self) -> bool:
|
||||||
|
try:
|
||||||
|
await self.web.locate(True)
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def _get_fans(self, web_userpanel: dict = None) -> List[Fan]:
|
||||||
|
if web_userpanel is None:
|
||||||
|
try:
|
||||||
|
web_userpanel = await self.web.userpanel()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_userpanel is not None:
|
||||||
|
try:
|
||||||
|
return [Fan(spd) for spd in web_userpanel["fans"]]
|
||||||
|
except (LookupError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_mac(self, web_userpanel: dict = None) -> Optional[str]:
|
||||||
|
if web_userpanel is None:
|
||||||
|
try:
|
||||||
|
web_userpanel = await self.web.userpanel()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_userpanel is not None:
|
||||||
|
try:
|
||||||
|
return web_userpanel["mac"].upper().replace("-", ":")
|
||||||
|
except (LookupError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_hostname(self, web_userpanel: dict = None) -> Optional[str]:
|
||||||
|
if web_userpanel is None:
|
||||||
|
try:
|
||||||
|
web_userpanel = await self.web.userpanel()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_userpanel is not None:
|
||||||
|
try:
|
||||||
|
return web_userpanel["host"]
|
||||||
|
except (LookupError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_hashrate(self, web_userpanel: dict = None) -> Optional[AlgoHashRate]:
|
||||||
|
if web_userpanel is None:
|
||||||
|
try:
|
||||||
|
web_userpanel = await self.web.userpanel()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_userpanel is not None:
|
||||||
|
try:
|
||||||
|
base_unit = web_userpanel["unit"]
|
||||||
|
return AlgoHashRate.SHA256(
|
||||||
|
float(web_userpanel["rtpow"].replace(base_unit, "")),
|
||||||
|
unit=MinerAlgo.SHA256.unit.from_str(base_unit + "H"),
|
||||||
|
).into(MinerAlgo.SHA256.unit.default)
|
||||||
|
except (LookupError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_fault_light(self, web_userpanel: dict = None) -> bool:
|
||||||
|
if web_userpanel is None:
|
||||||
|
try:
|
||||||
|
web_userpanel = await self.web.userpanel()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_userpanel is not None:
|
||||||
|
try:
|
||||||
|
return web_userpanel["locate"]
|
||||||
|
except (LookupError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _is_mining(self, web_userpanel: dict = None) -> Optional[bool]:
|
||||||
|
if web_userpanel is None:
|
||||||
|
try:
|
||||||
|
web_userpanel = await self.web.userpanel()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_userpanel is not None:
|
||||||
|
try:
|
||||||
|
return web_userpanel["powstate"]
|
||||||
|
except (LookupError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_hashboards(self, web_userpanel: dict = None) -> List[HashBoard]:
|
||||||
|
if web_userpanel is None:
|
||||||
|
try:
|
||||||
|
web_userpanel = await self.web.userpanel()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
hb_list = [
|
||||||
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
|
for i in range(self.expected_hashboards)
|
||||||
|
]
|
||||||
|
|
||||||
|
if web_userpanel is not None:
|
||||||
|
try:
|
||||||
|
for board in web_userpanel["boards"]:
|
||||||
|
idx = board["no"] - 1
|
||||||
|
hb_list[idx].chip_temp = round(board["outtmp"])
|
||||||
|
hb_list[idx].temp = round(board["intmp"])
|
||||||
|
hb_list[idx].hashrate = AlgoHashRate.SHA256(
|
||||||
|
float(board["rtpow"].replace("G", "")), HashUnit.SHA256.GH
|
||||||
|
).into(self.algo.unit.default)
|
||||||
|
hb_list[idx].chips = board["chipnum"]
|
||||||
|
hb_list[idx].missing = False
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
return hb_list
|
||||||
|
|
||||||
|
async def _get_uptime(self, web_userpanel: dict = None) -> Optional[int]:
|
||||||
|
if web_userpanel is None:
|
||||||
|
try:
|
||||||
|
web_userpanel = await self.web.userpanel()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_userpanel is not None:
|
||||||
|
try:
|
||||||
|
runtime = web_userpanel["runtime"]
|
||||||
|
days, hours, minutes, seconds = runtime.split(":")
|
||||||
|
return (
|
||||||
|
(int(days) * 24 * 60 * 60)
|
||||||
|
+ (int(hours) * 60 * 60)
|
||||||
|
+ (int(minutes) * 60)
|
||||||
|
+ int(seconds)
|
||||||
|
)
|
||||||
|
except (LookupError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
import logging
|
||||||
|
|
||||||
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
|
||||||
@@ -146,6 +147,22 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
|
async def upgrade_firmware(self) -> bool:
|
||||||
|
"""
|
||||||
|
Upgrade the firmware on a LuxOS miner by calling the 'updaterun' API command.
|
||||||
|
Returns:
|
||||||
|
bool: True if the firmware upgrade was successfully initiated, False otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await self.rpc.upgraderun()
|
||||||
|
logging.info(f"{self.ip}: Firmware upgrade initiated successfully.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except APIError as e:
|
||||||
|
logging.error(f"{self.ip}: Firmware upgrade failed: {e}")
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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(
|
||||||
**{
|
**{
|
||||||
@@ -60,6 +61,10 @@ MARA_DATA_LOC = DataLocations(
|
|||||||
"_get_uptime",
|
"_get_uptime",
|
||||||
[WebAPICommand("web_brief", "brief")],
|
[WebAPICommand("web_brief", "brief")],
|
||||||
),
|
),
|
||||||
|
str(DataOptions.POOLS): DataFunction(
|
||||||
|
"_get_pools",
|
||||||
|
[WebAPICommand("web_pools", "pools")],
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -305,3 +310,40 @@ class MaraMiner(MaraFirmware):
|
|||||||
return web_miner_config["mode"]["concorde"]["power-target"]
|
return web_miner_config["mode"]["concorde"]["power-target"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def _get_pools(self, web_pools: list = None) -> List[PoolMetrics]:
|
||||||
|
if web_pools is None:
|
||||||
|
try:
|
||||||
|
web_pools = await self.web.pools()
|
||||||
|
except APIError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
active_pool_index = None
|
||||||
|
highest_priority = float('inf')
|
||||||
|
|
||||||
|
for pool_info in web_pools:
|
||||||
|
if pool_info.get("status") == "Alive" and pool_info.get("priority", float('inf')) < highest_priority:
|
||||||
|
highest_priority = pool_info.get["priority"]
|
||||||
|
active_pool_index = pool_info["index"]
|
||||||
|
|
||||||
|
pools_data = []
|
||||||
|
if web_pools is not None:
|
||||||
|
try:
|
||||||
|
for pool_info in web_pools:
|
||||||
|
url = pool_info.get("url")
|
||||||
|
pool_url = PoolUrl.from_str(url) if url else None
|
||||||
|
pool_data = PoolMetrics(
|
||||||
|
accepted=pool_info.get("accepted"),
|
||||||
|
rejected=pool_info.get("rejected"),
|
||||||
|
get_failures=pool_info.get("stale"),
|
||||||
|
remote_failures=pool_info.get("discarded"),
|
||||||
|
active=pool_info.get("index") == active_pool_index,
|
||||||
|
alive=pool_info.get("status") == "Alive",
|
||||||
|
url=pool_url,
|
||||||
|
user=pool_info.get("user"),
|
||||||
|
index=pool_info.get("index"),
|
||||||
|
)
|
||||||
|
pools_data.append(pool_data)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
return pools_data
|
||||||
|
|||||||
@@ -48,3 +48,7 @@ class ePICMake(BaseMiner):
|
|||||||
|
|
||||||
class BitAxeMake(BaseMiner):
|
class BitAxeMake(BaseMiner):
|
||||||
make = MinerMake.BITAXE
|
make = MinerMake.BITAXE
|
||||||
|
|
||||||
|
|
||||||
|
class IceRiverMake(BaseMiner):
|
||||||
|
make = MinerMake.BITAXE
|
||||||
|
|||||||
@@ -19,5 +19,6 @@ from .auradine import *
|
|||||||
from .avalonminer import *
|
from .avalonminer import *
|
||||||
from .epic import *
|
from .epic import *
|
||||||
from .goldshell import *
|
from .goldshell import *
|
||||||
|
from .iceriver import *
|
||||||
from .innosilicon import *
|
from .innosilicon import *
|
||||||
from .whatsminer import *
|
from .whatsminer import *
|
||||||
|
|||||||
23
pyasic/miners/device/models/antminer/X3/KA3.py
Normal file
23
pyasic/miners/device/models/antminer/X3/KA3.py
Normal 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 KA3(AntMinerMake):
|
||||||
|
raw_model = MinerModel.ANTMINER.KA3
|
||||||
|
|
||||||
|
expected_chips = 92
|
||||||
24
pyasic/miners/device/models/antminer/X3/KS3.py
Normal file
24
pyasic/miners/device/models/antminer/X3/KS3.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
from pyasic.device.models import MinerModel
|
||||||
|
from pyasic.miners.device.makes import AntMinerMake
|
||||||
|
|
||||||
|
|
||||||
|
class KS3(AntMinerMake):
|
||||||
|
raw_model = MinerModel.ANTMINER.KS3
|
||||||
|
|
||||||
|
expected_chips = 92
|
||||||
|
expected_fans = 2
|
||||||
@@ -15,4 +15,6 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from .D3 import D3
|
from .D3 import D3
|
||||||
from .HS3 import HS3
|
from .HS3 import HS3
|
||||||
|
from .KA3 import KA3
|
||||||
|
from .KS3 import KS3
|
||||||
from .L3 import L3Plus
|
from .L3 import L3Plus
|
||||||
|
|||||||
23
pyasic/miners/device/models/antminer/X5/KS5.py
Normal file
23
pyasic/miners/device/models/antminer/X5/KS5.py
Normal 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 KS5(AntMinerMake):
|
||||||
|
raw_model = MinerModel.ANTMINER.KS5
|
||||||
|
|
||||||
|
expected_chips = 92
|
||||||
@@ -14,3 +14,4 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from .DR5 import DR5
|
from .DR5 import DR5
|
||||||
|
from .KS5 import KS5
|
||||||
|
|||||||
@@ -20,3 +20,4 @@ from .A9X import *
|
|||||||
from .A10X import *
|
from .A10X import *
|
||||||
from .A11X import *
|
from .A11X import *
|
||||||
from .A12X import *
|
from .A12X import *
|
||||||
|
from .nano import *
|
||||||
|
|||||||
1
pyasic/miners/device/models/avalonminer/nano/__init__.py
Normal file
1
pyasic/miners/device/models/avalonminer/nano/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .nano3 import AvalonNano3
|
||||||
10
pyasic/miners/device/models/avalonminer/nano/nano3.py
Normal file
10
pyasic/miners/device/models/avalonminer/nano/nano3.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from pyasic.device import MinerModel
|
||||||
|
from pyasic.miners.device.makes import AvalonMinerMake
|
||||||
|
|
||||||
|
|
||||||
|
class AvalonNano3(AvalonMinerMake):
|
||||||
|
raw_model = MinerModel.AVALONMINER.AvalonNano3
|
||||||
|
|
||||||
|
expected_hashboards = 1
|
||||||
|
expected_chips = 10
|
||||||
|
expected_fans = 1
|
||||||
23
pyasic/miners/device/models/iceriver/KSX/KS2.py
Normal file
23
pyasic/miners/device/models/iceriver/KSX/KS2.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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 KS2(IceRiverMake):
|
||||||
|
raw_model = MinerModel.ICERIVER.KS2
|
||||||
|
|
||||||
|
expected_fans = 4
|
||||||
1
pyasic/miners/device/models/iceriver/KSX/__init__.py
Normal file
1
pyasic/miners/device/models/iceriver/KSX/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .KS2 import KS2
|
||||||
1
pyasic/miners/device/models/iceriver/__init__.py
Normal file
1
pyasic/miners/device/models/iceriver/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .KSX import *
|
||||||
21
pyasic/miners/device/models/innosilicon/A11X/A11M.py
Normal file
21
pyasic/miners/device/models/innosilicon/A11X/A11M.py
Normal 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.device.models import MinerModel
|
||||||
|
from pyasic.miners.device.makes import InnosiliconMake
|
||||||
|
|
||||||
|
|
||||||
|
class A11MX(InnosiliconMake):
|
||||||
|
raw_model = MinerModel.INNOSILICON.A11MX
|
||||||
16
pyasic/miners/device/models/innosilicon/A11X/__init__.py
Normal file
16
pyasic/miners/device/models/innosilicon/A11X/__init__.py
Normal 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 .A11M import *
|
||||||
@@ -15,4 +15,5 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from .A10X import *
|
from .A10X import *
|
||||||
|
from .A11X import *
|
||||||
from .T3X import *
|
from .T3X import *
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ class M50VH60(WhatsMinerMake):
|
|||||||
class M50VH70(WhatsMinerMake):
|
class M50VH70(WhatsMinerMake):
|
||||||
raw_model = MinerModel.WHATSMINER.M50VH70
|
raw_model = MinerModel.WHATSMINER.M50VH70
|
||||||
|
|
||||||
|
expected_chips = 105
|
||||||
|
|
||||||
|
|
||||||
class M50VH80(WhatsMinerMake):
|
class M50VH80(WhatsMinerMake):
|
||||||
raw_model = MinerModel.WHATSMINER.M50VH80
|
raw_model = MinerModel.WHATSMINER.M50VH80
|
||||||
@@ -83,6 +85,10 @@ class M50VJ10(WhatsMinerMake):
|
|||||||
class M50VJ20(WhatsMinerMake):
|
class M50VJ20(WhatsMinerMake):
|
||||||
raw_model = MinerModel.WHATSMINER.M50VJ20
|
raw_model = MinerModel.WHATSMINER.M50VJ20
|
||||||
|
|
||||||
|
expected_chips = 111
|
||||||
|
|
||||||
|
|
||||||
class M50VJ30(WhatsMinerMake):
|
class M50VJ30(WhatsMinerMake):
|
||||||
raw_model = MinerModel.WHATSMINER.M50VJ30
|
raw_model = MinerModel.WHATSMINER.M50VJ30
|
||||||
|
|
||||||
|
expected_chips = 117
|
||||||
|
|||||||
@@ -51,3 +51,5 @@ class M50SVH40(WhatsMinerMake):
|
|||||||
|
|
||||||
class M50SVH50(WhatsMinerMake):
|
class M50SVH50(WhatsMinerMake):
|
||||||
raw_model = MinerModel.WHATSMINER.M50SVH50
|
raw_model = MinerModel.WHATSMINER.M50SVH50
|
||||||
|
|
||||||
|
expected_chips = 135
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ class M60VK10(WhatsMinerMake):
|
|||||||
class M60VK20(WhatsMinerMake):
|
class M60VK20(WhatsMinerMake):
|
||||||
raw_model = MinerModel.WHATSMINER.M60VK20
|
raw_model = MinerModel.WHATSMINER.M60VK20
|
||||||
|
|
||||||
|
expected_chips = 172
|
||||||
|
|
||||||
|
|
||||||
class M60VK30(WhatsMinerMake):
|
class M60VK30(WhatsMinerMake):
|
||||||
raw_model = MinerModel.WHATSMINER.M60VK30
|
raw_model = MinerModel.WHATSMINER.M60VK30
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import enum
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import warnings
|
||||||
|
from logging import warning
|
||||||
from typing import Any, AsyncGenerator, Callable
|
from typing import Any, AsyncGenerator, Callable
|
||||||
|
|
||||||
import anyio
|
import anyio
|
||||||
@@ -38,6 +40,7 @@ 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.iceriver import *
|
||||||
from pyasic.miners.innosilicon import *
|
from pyasic.miners.innosilicon import *
|
||||||
from pyasic.miners.whatsminer import *
|
from pyasic.miners.whatsminer import *
|
||||||
|
|
||||||
@@ -56,6 +59,7 @@ class MinerTypes(enum.Enum):
|
|||||||
AURADINE = 10
|
AURADINE = 10
|
||||||
MARATHON = 11
|
MARATHON = 11
|
||||||
BITAXE = 12
|
BITAXE = 12
|
||||||
|
ICERIVER = 13
|
||||||
|
|
||||||
|
|
||||||
MINER_CLASSES = {
|
MINER_CLASSES = {
|
||||||
@@ -64,7 +68,10 @@ MINER_CLASSES = {
|
|||||||
"ANTMINER D3": CGMinerD3,
|
"ANTMINER D3": CGMinerD3,
|
||||||
"ANTMINER HS3": BMMinerHS3,
|
"ANTMINER HS3": BMMinerHS3,
|
||||||
"ANTMINER L3+": BMMinerL3Plus,
|
"ANTMINER L3+": BMMinerL3Plus,
|
||||||
|
"ANTMINER KA3": BMMinerKA3,
|
||||||
|
"ANTMINER KS3": BMMinerKS3,
|
||||||
"ANTMINER DR5": CGMinerDR5,
|
"ANTMINER DR5": CGMinerDR5,
|
||||||
|
"ANTMINER KS5": BMMinerKS5,
|
||||||
"ANTMINER L7": BMMinerL7,
|
"ANTMINER L7": BMMinerL7,
|
||||||
"ANTMINER E9 PRO": BMMinerE9Pro,
|
"ANTMINER E9 PRO": BMMinerE9Pro,
|
||||||
"ANTMINER S9": BMMinerS9,
|
"ANTMINER S9": BMMinerS9,
|
||||||
@@ -330,11 +337,13 @@ MINER_CLASSES = {
|
|||||||
"AVALONMINER 1066": CGMinerAvalon1066,
|
"AVALONMINER 1066": CGMinerAvalon1066,
|
||||||
"AVALONMINER 1166PRO": CGMinerAvalon1166Pro,
|
"AVALONMINER 1166PRO": CGMinerAvalon1166Pro,
|
||||||
"AVALONMINER 1246": CGMinerAvalon1246,
|
"AVALONMINER 1246": CGMinerAvalon1246,
|
||||||
|
"AVALONMINER NANO3": CGMinerAvalonNano3,
|
||||||
},
|
},
|
||||||
MinerTypes.INNOSILICON: {
|
MinerTypes.INNOSILICON: {
|
||||||
None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}),
|
None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}),
|
||||||
"T3H+": InnosiliconT3HPlus,
|
"T3H+": InnosiliconT3HPlus,
|
||||||
"A10X": InnosiliconA10X,
|
"A10X": InnosiliconA10X,
|
||||||
|
"A11MX": InnosiliconA11MX,
|
||||||
},
|
},
|
||||||
MinerTypes.GOLDSHELL: {
|
MinerTypes.GOLDSHELL: {
|
||||||
None: type("GoldshellUnknown", (GoldshellMiner, GoldshellMake), {}),
|
None: type("GoldshellUnknown", (GoldshellMiner, GoldshellMake), {}),
|
||||||
@@ -373,6 +382,7 @@ MINER_CLASSES = {
|
|||||||
"ANTMINER S19 PRO+ HYD.": BOSMinerS19ProPlusHydro,
|
"ANTMINER S19 PRO+ HYD.": BOSMinerS19ProPlusHydro,
|
||||||
"ANTMINER T19": BOSMinerT19,
|
"ANTMINER T19": BOSMinerT19,
|
||||||
"ANTMINER S21": BOSMinerS21,
|
"ANTMINER S21": BOSMinerS21,
|
||||||
|
"ANTMINER T21": BOSMinerT21,
|
||||||
},
|
},
|
||||||
MinerTypes.VNISH: {
|
MinerTypes.VNISH: {
|
||||||
None: VNish,
|
None: VNish,
|
||||||
@@ -451,6 +461,10 @@ MINER_CLASSES = {
|
|||||||
"BM1366": BitAxeUltra,
|
"BM1366": BitAxeUltra,
|
||||||
"BM1397": BitAxeMax,
|
"BM1397": BitAxeMax,
|
||||||
},
|
},
|
||||||
|
MinerTypes.ICERIVER: {
|
||||||
|
None: type("IceRiverUnknown", (IceRiver, IceRiverMake), {}),
|
||||||
|
"KS2": IceRiverKS2,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -623,6 +637,8 @@ class MinerFactory:
|
|||||||
return MinerTypes.INNOSILICON
|
return MinerTypes.INNOSILICON
|
||||||
if "Miner UI" in web_text:
|
if "Miner UI" in web_text:
|
||||||
return MinerTypes.AURADINE
|
return MinerTypes.AURADINE
|
||||||
|
if "<TITLE>用户界面</TITLE>" in web_text:
|
||||||
|
return MinerTypes.ICERIVER
|
||||||
|
|
||||||
async def _get_miner_socket(self, ip: str) -> MinerTypes | None:
|
async def _get_miner_socket(self, ip: str) -> MinerTypes | None:
|
||||||
commands = ["version", "devdetails"]
|
commands = ["version", "devdetails"]
|
||||||
@@ -689,8 +705,6 @@ 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 "VNISH" in upper_data or "DEVICE PATH" in upper_data:
|
|
||||||
return MinerTypes.VNISH
|
|
||||||
if "HIVEON" in upper_data:
|
if "HIVEON" in upper_data:
|
||||||
return MinerTypes.HIVEON
|
return MinerTypes.HIVEON
|
||||||
if "LUXMINER" in upper_data:
|
if "LUXMINER" in upper_data:
|
||||||
@@ -705,10 +719,14 @@ class MinerFactory:
|
|||||||
or "BFGMINER" in upper_data
|
or "BFGMINER" in upper_data
|
||||||
):
|
):
|
||||||
return MinerTypes.GOLDSHELL
|
return MinerTypes.GOLDSHELL
|
||||||
|
if "INNOMINER" in upper_data:
|
||||||
|
return MinerTypes.INNOSILICON
|
||||||
if "AVALON" in upper_data:
|
if "AVALON" in upper_data:
|
||||||
return MinerTypes.AVALONMINER
|
return MinerTypes.AVALONMINER
|
||||||
if "GCMINER" in upper_data or "FLUXOS" in upper_data:
|
if "GCMINER" in upper_data or "FLUXOS" in upper_data:
|
||||||
return MinerTypes.AURADINE
|
return MinerTypes.AURADINE
|
||||||
|
if "VNISH" in upper_data:
|
||||||
|
return MinerTypes.VNISH
|
||||||
|
|
||||||
async def send_web_command(
|
async def send_web_command(
|
||||||
self,
|
self,
|
||||||
@@ -798,7 +816,9 @@ class MinerFactory:
|
|||||||
str_data = str_data.replace("info", "1nfo")
|
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("1nfo", "info")
|
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("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:]}"
|
||||||
@@ -823,6 +843,10 @@ class MinerFactory:
|
|||||||
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip)
|
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
if miner_type in MINER_CLASSES:
|
if miner_type in MINER_CLASSES:
|
||||||
|
warnings.warn(
|
||||||
|
f"Partially supported miner found: {miner_model}, please open an issue with miner data "
|
||||||
|
f"and this model on GitHub (https://github.com/UpstreamData/pyasic/issues)."
|
||||||
|
)
|
||||||
return MINER_CLASSES[miner_type][None](ip)
|
return MINER_CLASSES[miner_type][None](ip)
|
||||||
return UnknownMiner(str(ip))
|
return UnknownMiner(str(ip))
|
||||||
|
|
||||||
@@ -898,10 +922,12 @@ class MinerFactory:
|
|||||||
async def get_miner_model_avalonminer(self, ip: str) -> str | None:
|
async def get_miner_model_avalonminer(self, ip: str) -> str | None:
|
||||||
sock_json_data = await self.send_api_command(ip, "version")
|
sock_json_data = await self.send_api_command(ip, "version")
|
||||||
try:
|
try:
|
||||||
miner_model = sock_json_data["VERSION"][0]["PROD"]
|
miner_model = sock_json_data["VERSION"][0]["PROD"].upper()
|
||||||
if "-" in miner_model:
|
if "-" in miner_model:
|
||||||
miner_model = miner_model.split("-")[0]
|
miner_model = miner_model.split("-")[0]
|
||||||
|
if miner_model in ["AVALONNANO", "AVALON0O"]:
|
||||||
|
nano_subtype = sock_json_data["VERSION"][0]["MODEL"].upper()
|
||||||
|
miner_model = f"AVALONMINER {nano_subtype}"
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|||||||
1
pyasic/miners/iceriver/__init__.py
Normal file
1
pyasic/miners/iceriver/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .iceminer import *
|
||||||
6
pyasic/miners/iceriver/iceminer/KSX/KS2.py
Normal file
6
pyasic/miners/iceriver/iceminer/KSX/KS2.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pyasic.miners.backends.iceriver import IceRiver
|
||||||
|
from pyasic.miners.device.models import KS2
|
||||||
|
|
||||||
|
|
||||||
|
class IceRiverKS2(IceRiver, KS2):
|
||||||
|
pass
|
||||||
1
pyasic/miners/iceriver/iceminer/KSX/__init__.py
Normal file
1
pyasic/miners/iceriver/iceminer/KSX/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .KS2 import IceRiverKS2
|
||||||
1
pyasic/miners/iceriver/iceminer/__init__.py
Normal file
1
pyasic/miners/iceriver/iceminer/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .KSX import *
|
||||||
22
pyasic/miners/innosilicon/cgminer/A11X/A11M.py
Normal file
22
pyasic/miners/innosilicon/cgminer/A11X/A11M.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from pyasic.miners.backends.innosilicon import Innosilicon
|
||||||
|
from pyasic.miners.device.models import A11MX
|
||||||
|
|
||||||
|
|
||||||
|
class InnosiliconA11MX(Innosilicon, A11MX):
|
||||||
|
pass
|
||||||
1
pyasic/miners/innosilicon/cgminer/A11X/__init__.py
Normal file
1
pyasic/miners/innosilicon/cgminer/A11X/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .A11M import InnosiliconA11MX
|
||||||
@@ -15,4 +15,5 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from .A10X import *
|
from .A10X import *
|
||||||
|
from .A11X import *
|
||||||
from .T3X import *
|
from .T3X import *
|
||||||
|
|||||||
@@ -268,10 +268,9 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
# 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("nan", "0")
|
|
||||||
# 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:]}"
|
||||||
|
|||||||
@@ -749,3 +749,12 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
return await self.send_command("wakeup", parameters=session_id)
|
return await self.send_command("wakeup", parameters=session_id)
|
||||||
|
|
||||||
|
async def upgraderun(self):
|
||||||
|
"""
|
||||||
|
Send the 'updaterun' command to the miner.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The response from the miner after sending the 'updaterun' command.
|
||||||
|
"""
|
||||||
|
return await self.send_command("updaterun")
|
||||||
@@ -37,6 +37,7 @@ _settings = { # defaults
|
|||||||
"default_auradine_web_password": "admin",
|
"default_auradine_web_password": "admin",
|
||||||
"default_epic_web_password": "letmein",
|
"default_epic_web_password": "letmein",
|
||||||
"default_hive_web_password": "admin",
|
"default_hive_web_password": "admin",
|
||||||
|
"default_iceriver_web_password": "12345678",
|
||||||
"default_antminer_ssh_password": "miner",
|
"default_antminer_ssh_password": "miner",
|
||||||
"default_bosminer_ssh_password": "root",
|
"default_bosminer_ssh_password": "root",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,6 @@ 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 .iceriver import IceRiverWebAPI
|
||||||
from .innosilicon import InnosiliconWebAPI
|
from .innosilicon import InnosiliconWebAPI
|
||||||
from .vnish import VNishWebAPI
|
from .vnish import VNishWebAPI
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
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
|
||||||
@@ -59,9 +60,8 @@ class AntminerModernWebAPI(BaseWebAPI):
|
|||||||
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
|
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
|
||||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(
|
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||||
transport=settings.transport(),
|
|
||||||
) as client:
|
|
||||||
if parameters:
|
if parameters:
|
||||||
data = await client.post(
|
data = await client.post(
|
||||||
url,
|
url,
|
||||||
@@ -71,14 +71,15 @@ class AntminerModernWebAPI(BaseWebAPI):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
data = await client.get(url, auth=auth)
|
data = await client.get(url, auth=auth)
|
||||||
except httpx.HTTPError:
|
except httpx.HTTPError as e:
|
||||||
pass
|
return {"success": False, "message": f"HTTP error occurred: {str(e)}"}
|
||||||
else:
|
else:
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
try:
|
try:
|
||||||
return data.json()
|
return data.json()
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
pass
|
return {"success": False, "message": "Failed to decode JSON"}
|
||||||
|
return {"success": False, "message": "Unknown error occurred"}
|
||||||
|
|
||||||
async def multicommand(
|
async def multicommand(
|
||||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||||
@@ -403,3 +404,20 @@ class AntminerOldWebAPI(BaseWebAPI):
|
|||||||
dict: Information about the mining pools configured in the miner.
|
dict: Information about the mining pools configured in the miner.
|
||||||
"""
|
"""
|
||||||
return await self.send_command("miner_pools")
|
return await self.send_command("miner_pools")
|
||||||
|
|
||||||
|
async def update_firmware(self, file: Path, keep_settings: bool = True) -> dict:
|
||||||
|
"""Perform a system update by uploading a firmware file and sending a command to initiate the update."""
|
||||||
|
|
||||||
|
async with aiofiles.open(file, "rb") as firmware:
|
||||||
|
file_content = await firmware.read()
|
||||||
|
|
||||||
|
parameters = {
|
||||||
|
"file": (file.name, file_content, "application/octet-stream"),
|
||||||
|
"filename": file.name,
|
||||||
|
"keep_settings": keep_settings
|
||||||
|
}
|
||||||
|
|
||||||
|
return await self.send_command(
|
||||||
|
command="upgrade",
|
||||||
|
**parameters
|
||||||
|
)
|
||||||
|
|||||||
93
pyasic/web/braiins_os/better_monkey.py
Normal file
93
pyasic/web/braiins_os/better_monkey.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from betterproto import DATETIME_ZERO, TYPE_MAP, TYPE_MESSAGE, Casing, Message
|
||||||
|
|
||||||
|
|
||||||
|
# https://github.com/danielgtaylor/python-betterproto/pull/609
|
||||||
|
def to_pydict(
|
||||||
|
self, casing: Casing = Casing.CAMEL, include_default_values: bool = False
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Returns a python dict representation of this object.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
casing: :class:`Casing`
|
||||||
|
The casing to use for key values. Default is :attr:`Casing.CAMEL` for
|
||||||
|
compatibility purposes.
|
||||||
|
include_default_values: :class:`bool`
|
||||||
|
If ``True`` will include the default values of fields. Default is ``False``.
|
||||||
|
E.g. an ``int32`` field will be included with a value of ``0`` if this is
|
||||||
|
set to ``True``, otherwise this would be ignored.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Dict[:class:`str`, Any]
|
||||||
|
The python dict representation of this object.
|
||||||
|
"""
|
||||||
|
output: Dict[str, Any] = {}
|
||||||
|
defaults = self._betterproto.default_gen
|
||||||
|
for field_name, meta in self._betterproto.meta_by_field_name.items():
|
||||||
|
field_is_repeated = defaults[field_name] is list
|
||||||
|
try:
|
||||||
|
value = getattr(self, field_name)
|
||||||
|
except AttributeError:
|
||||||
|
value = self._get_field_default(field_name)
|
||||||
|
cased_name = casing(field_name).rstrip("_") # type: ignore
|
||||||
|
if meta.proto_type == TYPE_MESSAGE:
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
if (
|
||||||
|
value != DATETIME_ZERO
|
||||||
|
or include_default_values
|
||||||
|
or self._include_default_value_for_oneof(
|
||||||
|
field_name=field_name, meta=meta
|
||||||
|
)
|
||||||
|
):
|
||||||
|
output[cased_name] = value
|
||||||
|
elif isinstance(value, timedelta):
|
||||||
|
if (
|
||||||
|
value != timedelta(0)
|
||||||
|
or include_default_values
|
||||||
|
or self._include_default_value_for_oneof(
|
||||||
|
field_name=field_name, meta=meta
|
||||||
|
)
|
||||||
|
):
|
||||||
|
output[cased_name] = value
|
||||||
|
elif meta.wraps:
|
||||||
|
if value is not None or include_default_values:
|
||||||
|
output[cased_name] = value
|
||||||
|
elif field_is_repeated:
|
||||||
|
# Convert each item.
|
||||||
|
value = [i.to_pydict(casing, include_default_values) for i in value]
|
||||||
|
if value or include_default_values:
|
||||||
|
output[cased_name] = value
|
||||||
|
elif value is None:
|
||||||
|
if include_default_values:
|
||||||
|
output[cased_name] = None
|
||||||
|
elif (
|
||||||
|
value._serialized_on_wire
|
||||||
|
or include_default_values
|
||||||
|
or self._include_default_value_for_oneof(
|
||||||
|
field_name=field_name, meta=meta
|
||||||
|
)
|
||||||
|
):
|
||||||
|
output[cased_name] = value.to_pydict(casing, include_default_values)
|
||||||
|
elif meta.proto_type == TYPE_MAP:
|
||||||
|
for k in value:
|
||||||
|
if hasattr(value[k], "to_pydict"):
|
||||||
|
value[k] = value[k].to_pydict(casing, include_default_values)
|
||||||
|
|
||||||
|
if value or include_default_values:
|
||||||
|
output[cased_name] = value
|
||||||
|
elif (
|
||||||
|
value != self._get_field_default(field_name)
|
||||||
|
or include_default_values
|
||||||
|
or self._include_default_value_for_oneof(field_name=field_name, meta=meta)
|
||||||
|
):
|
||||||
|
output[cased_name] = value
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def patch():
|
||||||
|
Message.to_pydict = to_pydict
|
||||||
@@ -26,6 +26,9 @@ from grpclib.client import Channel
|
|||||||
from pyasic import settings
|
from pyasic import settings
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.web.base import BaseWebAPI
|
from pyasic.web.base import BaseWebAPI
|
||||||
|
from pyasic.web.braiins_os.better_monkey import patch
|
||||||
|
|
||||||
|
patch()
|
||||||
|
|
||||||
from .proto.braiins.bos import *
|
from .proto.braiins.bos import *
|
||||||
from .proto.braiins.bos.v1 import *
|
from .proto.braiins.bos.v1 import *
|
||||||
@@ -206,7 +209,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
async def set_immersion_mode(
|
async def set_immersion_mode(
|
||||||
self,
|
self,
|
||||||
enable: bool,
|
enable: bool,
|
||||||
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
"set_immersion_mode",
|
"set_immersion_mode",
|
||||||
@@ -227,7 +230,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def set_default_power_target(
|
async def set_default_power_target(
|
||||||
self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY
|
self, save_action: SaveAction = SaveAction.SAVE_AND_APPLY
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
"set_default_power_target",
|
"set_default_power_target",
|
||||||
@@ -238,7 +241,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
async def set_power_target(
|
async def set_power_target(
|
||||||
self,
|
self,
|
||||||
power_target: int,
|
power_target: int,
|
||||||
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
"set_power_target",
|
"set_power_target",
|
||||||
@@ -251,7 +254,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
async def increment_power_target(
|
async def increment_power_target(
|
||||||
self,
|
self,
|
||||||
power_target_increment: int,
|
power_target_increment: int,
|
||||||
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
"increment_power_target",
|
"increment_power_target",
|
||||||
@@ -265,7 +268,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
async def decrement_power_target(
|
async def decrement_power_target(
|
||||||
self,
|
self,
|
||||||
power_target_decrement: int,
|
power_target_decrement: int,
|
||||||
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
"decrement_power_target",
|
"decrement_power_target",
|
||||||
@@ -277,7 +280,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def set_default_hashrate_target(
|
async def set_default_hashrate_target(
|
||||||
self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY
|
self, save_action: SaveAction = SaveAction.SAVE_AND_APPLY
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
"set_default_hashrate_target",
|
"set_default_hashrate_target",
|
||||||
@@ -288,7 +291,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
async def set_hashrate_target(
|
async def set_hashrate_target(
|
||||||
self,
|
self,
|
||||||
hashrate_target: float,
|
hashrate_target: float,
|
||||||
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
"set_hashrate_target",
|
"set_hashrate_target",
|
||||||
@@ -302,7 +305,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
async def increment_hashrate_target(
|
async def increment_hashrate_target(
|
||||||
self,
|
self,
|
||||||
hashrate_target_increment: int,
|
hashrate_target_increment: int,
|
||||||
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
"increment_hashrate_target",
|
"increment_hashrate_target",
|
||||||
@@ -318,7 +321,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
async def decrement_hashrate_target(
|
async def decrement_hashrate_target(
|
||||||
self,
|
self,
|
||||||
hashrate_target_decrement: int,
|
hashrate_target_decrement: int,
|
||||||
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
"decrement_hashrate_target",
|
"decrement_hashrate_target",
|
||||||
@@ -359,7 +362,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
self,
|
self,
|
||||||
wattage_target: int = None,
|
wattage_target: int = None,
|
||||||
hashrate_target: int = None,
|
hashrate_target: int = None,
|
||||||
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
if wattage_target is not None and hashrate_target is not None:
|
if wattage_target is not None and hashrate_target is not None:
|
||||||
logging.error(
|
logging.error(
|
||||||
@@ -459,7 +462,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
async def enable_hashboards(
|
async def enable_hashboards(
|
||||||
self,
|
self,
|
||||||
hashboard_ids: List[str],
|
hashboard_ids: List[str],
|
||||||
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
"enable_hashboards",
|
"enable_hashboards",
|
||||||
@@ -472,7 +475,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
async def disable_hashboards(
|
async def disable_hashboards(
|
||||||
self,
|
self,
|
||||||
hashboard_ids: List[str],
|
hashboard_ids: List[str],
|
||||||
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
"disable_hashboards",
|
"disable_hashboards",
|
||||||
@@ -485,7 +488,7 @@ class BOSerWebAPI(BaseWebAPI):
|
|||||||
async def set_pool_groups(
|
async def set_pool_groups(
|
||||||
self,
|
self,
|
||||||
pool_groups: List[PoolGroupConfiguration],
|
pool_groups: List[PoolGroupConfiguration],
|
||||||
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
"set_pool_groups",
|
"set_pool_groups",
|
||||||
|
|||||||
@@ -4,12 +4,17 @@
|
|||||||
# This file has been @generated
|
# This file has been @generated
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Dict, Optional
|
from typing import (
|
||||||
|
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
|
||||||
@@ -18,7 +23,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
class ApiVersion(betterproto.Message):
|
class ApiVersion(betterproto.Message):
|
||||||
"""LATEST_API_VERSION=1.2.0"""
|
"""LATEST_API_VERSION=1.3.0"""
|
||||||
|
|
||||||
major: int = betterproto.uint64_field(1)
|
major: int = betterproto.uint64_field(1)
|
||||||
minor: int = betterproto.uint64_field(2)
|
minor: int = betterproto.uint64_field(2)
|
||||||
|
|||||||
@@ -5,12 +5,19 @@
|
|||||||
import warnings
|
import warnings
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING, AsyncIterator, Dict, List, Optional
|
from typing import (
|
||||||
|
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
|
||||||
@@ -20,125 +27,129 @@ if TYPE_CHECKING:
|
|||||||
class SaveAction(betterproto.Enum):
|
class SaveAction(betterproto.Enum):
|
||||||
"""Save action for different operations"""
|
"""Save action for different operations"""
|
||||||
|
|
||||||
SAVE_ACTION_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
SAVE_ACTION_SAVE = 1
|
SAVE = 1
|
||||||
SAVE_ACTION_SAVE_AND_APPLY = 2
|
SAVE_AND_APPLY = 2
|
||||||
SAVE_ACTION_SAVE_AND_FORCE_APPLY = 3
|
SAVE_AND_FORCE_APPLY = 3
|
||||||
|
|
||||||
|
|
||||||
class CoolingMode(betterproto.Enum):
|
class CoolingMode(betterproto.Enum):
|
||||||
COOLING_MODE_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
COOLING_MODE_AUTO = 1
|
AUTO = 1
|
||||||
COOLING_MODE_MANUAL = 2
|
MANUAL = 2
|
||||||
COOLING_MODE_DISABLED = 3
|
DISABLED = 3
|
||||||
|
|
||||||
|
|
||||||
class SensorLocation(betterproto.Enum):
|
class SensorLocation(betterproto.Enum):
|
||||||
SENSOR_LOCATION_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
SENSOR_LOCATION_CHIP = 1
|
CHIP = 1
|
||||||
SENSOR_LOCATION_PCB = 2
|
PCB = 2
|
||||||
|
|
||||||
|
|
||||||
class TunerMode(betterproto.Enum):
|
class TunerMode(betterproto.Enum):
|
||||||
TUNER_MODE_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
TUNER_MODE_POWER_TARGET = 1
|
POWER_TARGET = 1
|
||||||
TUNER_MODE_HASHRATE_TARGET = 2
|
HASHRATE_TARGET = 2
|
||||||
|
|
||||||
|
|
||||||
class TunerState(betterproto.Enum):
|
class TunerState(betterproto.Enum):
|
||||||
TUNER_STATE_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
TUNER_STATE_DISABLED = 1
|
DISABLED = 1
|
||||||
TUNER_STATE_STABLE = 2
|
STABLE = 2
|
||||||
TUNER_STATE_TUNING = 3
|
TUNING = 3
|
||||||
TUNER_STATE_ERROR = 4
|
ERROR = 4
|
||||||
|
|
||||||
|
|
||||||
class LicenseType(betterproto.Enum):
|
class LicenseType(betterproto.Enum):
|
||||||
LICENSE_TYPE_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
LICENSE_TYPE_STANDARD = 1
|
STANDARD = 1
|
||||||
LICENSE_TYPE_CUSTOM = 2
|
CUSTOM = 2
|
||||||
|
|
||||||
|
|
||||||
class Platform(betterproto.Enum):
|
class Platform(betterproto.Enum):
|
||||||
"""Supported platforms"""
|
"""Supported platforms"""
|
||||||
|
|
||||||
PLATFORM_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
PLATFORM_AM1_S9 = 1
|
AM1_S9 = 1
|
||||||
PLATFORM_AM2_S17 = 2
|
AM2_S17 = 2
|
||||||
PLATFORM_AM3_BBB = 3
|
AM3_BBB = 3
|
||||||
PLATFORM_AM3_AML = 4
|
AM3_AML = 4
|
||||||
PLATFORM_STM32MP157C_II1_AM2 = 5
|
STM32MP157C_II1_AM2 = 5
|
||||||
PLATFORM_CVITEK_BM1_AM2 = 6
|
CVITEK_BM1_AM2 = 6
|
||||||
PLATFORM_ZYNQ_BM3_AM2 = 7
|
ZYNQ_BM3_AM2 = 7
|
||||||
|
STM32MP157C_II2_BMM1 = 8
|
||||||
|
|
||||||
|
|
||||||
class BosMode(betterproto.Enum):
|
class BosMode(betterproto.Enum):
|
||||||
"""BOS modes enumeration"""
|
"""BOS modes enumeration"""
|
||||||
|
|
||||||
BOS_MODE_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
BOS_MODE_UPGRADE = 1
|
UPGRADE = 1
|
||||||
BOS_MODE_RECOVERY = 2
|
RECOVERY = 2
|
||||||
BOS_MODE_SD = 3
|
SD = 3
|
||||||
BOS_MODE_NAND = 4
|
NAND = 4
|
||||||
BOS_MODE_EMMC = 5
|
EMMC = 5
|
||||||
|
|
||||||
|
|
||||||
class MinerBrand(betterproto.Enum):
|
class MinerBrand(betterproto.Enum):
|
||||||
MINER_BRAND_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
MINER_BRAND_ANTMINER = 1
|
ANTMINER = 1
|
||||||
MINER_BRAND_WHATSMINER = 2
|
WHATSMINER = 2
|
||||||
|
|
||||||
|
|
||||||
class MinerModel(betterproto.Enum):
|
class MinerModel(betterproto.Enum):
|
||||||
"""Deprecated: This enumeration is not longer maintained"""
|
"""Deprecated: This enumeration is not longer maintained"""
|
||||||
|
|
||||||
MINER_MODEL_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
MINER_MODEL_ANTMINER_S9 = 1
|
ANTMINER_S9 = 1
|
||||||
MINER_MODEL_ANTMINER_X17 = 2
|
ANTMINER_X17 = 2
|
||||||
MINER_MODEL_ANTMINER_S17 = 3
|
ANTMINER_S17 = 3
|
||||||
MINER_MODEL_ANTMINER_S17_PLUS = 4
|
ANTMINER_S17_PLUS = 4
|
||||||
MINER_MODEL_ANTMINER_S17_PRO = 5
|
ANTMINER_S17_PRO = 5
|
||||||
MINER_MODEL_ANTMINER_S17E = 6
|
ANTMINER_S17E = 6
|
||||||
MINER_MODEL_ANTMINER_T17 = 7
|
ANTMINER_T17 = 7
|
||||||
MINER_MODEL_ANTMINER_T17E = 8
|
ANTMINER_T17E = 8
|
||||||
MINER_MODEL_ANTMINER_T17_PLUS = 9
|
ANTMINER_T17_PLUS = 9
|
||||||
MINER_MODEL_ANTMINER_X19 = 10
|
ANTMINER_X19 = 10
|
||||||
MINER_MODEL_ANTMINER_S19 = 11
|
ANTMINER_S19 = 11
|
||||||
MINER_MODEL_ANTMINER_S19_PRO = 12
|
ANTMINER_S19_PRO = 12
|
||||||
MINER_MODEL_ANTMINER_S19_PLUS = 13
|
ANTMINER_S19_PLUS = 13
|
||||||
MINER_MODEL_ANTMINER_S19J = 14
|
ANTMINER_S19J = 14
|
||||||
MINER_MODEL_ANTMINER_S19J_PRO = 15
|
ANTMINER_S19J_PRO = 15
|
||||||
MINER_MODEL_ANTMINER_S19A = 16
|
ANTMINER_S19A = 16
|
||||||
MINER_MODEL_ANTMINER_S19A_PRO = 17
|
ANTMINER_S19A_PRO = 17
|
||||||
MINER_MODEL_ANTMINER_S19XP = 18
|
ANTMINER_S19XP = 18
|
||||||
MINER_MODEL_ANTMINER_T19 = 19
|
ANTMINER_T19 = 19
|
||||||
MINER_MODEL_ANTMINER_S19J_PRO_PLUS = 20
|
ANTMINER_S19J_PRO_PLUS = 20
|
||||||
|
|
||||||
|
|
||||||
class MinerStatus(betterproto.Enum):
|
class MinerStatus(betterproto.Enum):
|
||||||
MINER_STATUS_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
MINER_STATUS_NOT_STARTED = 1
|
NOT_STARTED = 1
|
||||||
MINER_STATUS_NORMAL = 2
|
NORMAL = 2
|
||||||
MINER_STATUS_PAUSED = 3
|
PAUSED = 3
|
||||||
MINER_STATUS_SUSPENDED = 4
|
SUSPENDED = 4
|
||||||
MINER_STATUS_RESTRICTED = 5
|
RESTRICTED = 5
|
||||||
|
|
||||||
|
|
||||||
class SupportArchiveFormat(betterproto.Enum):
|
class SupportArchiveFormat(betterproto.Enum):
|
||||||
"""Enumeration for support archive format"""
|
"""Enumeration for support archive format"""
|
||||||
|
|
||||||
SUPPORT_ARCHIVE_FORMAT_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
SUPPORT_ARCHIVE_FORMAT_ZIP = 1
|
ZIP = 1
|
||||||
"""Compressed zip format"""
|
"""Compressed zip format"""
|
||||||
|
|
||||||
SUPPORT_ARCHIVE_FORMAT_BOS = 2
|
BOS = 2
|
||||||
"""BOS custom format"""
|
"""BOS custom format"""
|
||||||
|
|
||||||
|
ZIP_ENCRYPTED = 3
|
||||||
|
"""Compressed encrypted zip format"""
|
||||||
|
|
||||||
|
|
||||||
class NetworkProtocol(betterproto.Enum):
|
class NetworkProtocol(betterproto.Enum):
|
||||||
NETWORK_PROTOCOL_UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
NETWORK_PROTOCOL_DHCP = 1
|
DHCP = 1
|
||||||
NETWORK_PROTOCOL_STATIC = 2
|
STATIC = 2
|
||||||
|
|
||||||
|
|
||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
@@ -268,8 +279,8 @@ class LoginResponse(betterproto.Message):
|
|||||||
|
|
||||||
timeout_s: int = betterproto.uint32_field(2)
|
timeout_s: int = betterproto.uint32_field(2)
|
||||||
"""
|
"""
|
||||||
Authentication token validity/timeout in seconds. Token validity refreshed
|
Authentication token validity/timeout in seconds.
|
||||||
to this value with each request.
|
Token validity refreshed to this value with each request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -277,9 +288,7 @@ class LoginResponse(betterproto.Message):
|
|||||||
class SetPasswordRequest(betterproto.Message):
|
class SetPasswordRequest(betterproto.Message):
|
||||||
"""Request for set password action."""
|
"""Request for set password action."""
|
||||||
|
|
||||||
password: Optional[str] = betterproto.string_field(
|
password: Optional[str] = betterproto.string_field(1, optional=True)
|
||||||
1, optional=True, group="_password"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
@@ -335,8 +344,8 @@ class BasesPoints(betterproto.Message):
|
|||||||
|
|
||||||
bsp: int = betterproto.uint32_field(1)
|
bsp: int = betterproto.uint32_field(1)
|
||||||
"""
|
"""
|
||||||
A basis point is one hundredth of 1 percentage point. For example: 1bps =
|
A basis point is one hundredth of 1 percentage point.
|
||||||
0.01%, 250bps = 2.5%
|
For example: 1bps = 0.01%, 250bps = 2.5%
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -409,9 +418,9 @@ class VoltageConstraints(betterproto.Message):
|
|||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
class CoolingAutoMode(betterproto.Message):
|
class CoolingAutoMode(betterproto.Message):
|
||||||
"""
|
"""
|
||||||
The temperature control modes. Miner software tries to regulate the fan
|
The temperature control modes.
|
||||||
speed so that miner temperature is approximately at the target temperature.
|
Miner software tries to regulate the fan speed so that miner temperature is approximately at the target temperature.
|
||||||
The allowed temperature range is 0-200 degree Celsius.
|
The allowed temperature range is 0-200 degree Celsius.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
target_temperature: "Temperature" = betterproto.message_field(1)
|
target_temperature: "Temperature" = betterproto.message_field(1)
|
||||||
@@ -422,8 +431,7 @@ class CoolingAutoMode(betterproto.Message):
|
|||||||
|
|
||||||
dangerous_temperature: "Temperature" = betterproto.message_field(3)
|
dangerous_temperature: "Temperature" = betterproto.message_field(3)
|
||||||
"""
|
"""
|
||||||
Temperature threshold at which BOSMiner shuts down in order to prevent
|
Temperature threshold at which BOSMiner shuts down in order to prevent overheating and damaging the miner.
|
||||||
overheating and damaging the miner.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -433,12 +441,11 @@ class CoolingManualMode(betterproto.Message):
|
|||||||
Fans are kept at a fixed, user-defined speed, no matter the temperature.
|
Fans are kept at a fixed, user-defined speed, no matter the temperature.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fan_speed_ratio: Optional[float] = betterproto.double_field(
|
fan_speed_ratio: Optional[float] = betterproto.double_field(1, optional=True)
|
||||||
1, optional=True, group="_fan_speed_ratio"
|
|
||||||
)
|
|
||||||
"""
|
"""
|
||||||
User defined fan speed expressed as a ratio between 0.0 and 1.0 where 0.0
|
User defined fan speed expressed as a ratio between 0.0 and 1.0
|
||||||
means completely turned off and 1.0 means running at full speed possible
|
where 0.0 means completely turned off and
|
||||||
|
1.0 means running at full speed possible
|
||||||
"""
|
"""
|
||||||
|
|
||||||
hot_temperature: "Temperature" = betterproto.message_field(2)
|
hot_temperature: "Temperature" = betterproto.message_field(2)
|
||||||
@@ -446,8 +453,7 @@ class CoolingManualMode(betterproto.Message):
|
|||||||
|
|
||||||
dangerous_temperature: "Temperature" = betterproto.message_field(3)
|
dangerous_temperature: "Temperature" = betterproto.message_field(3)
|
||||||
"""
|
"""
|
||||||
Temperature threshold at which BOSMiner shuts down in order to prevent
|
Temperature threshold at which BOSMiner shuts down in order to prevent overheating and damaging the miner.
|
||||||
overheating and damaging the miner.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -455,20 +461,17 @@ class CoolingManualMode(betterproto.Message):
|
|||||||
class CoolingDisabledMode(betterproto.Message):
|
class CoolingDisabledMode(betterproto.Message):
|
||||||
"""Disable temperature control. May be dangerous."""
|
"""Disable temperature control. May be dangerous."""
|
||||||
|
|
||||||
fan_speed_ratio: Optional[float] = betterproto.double_field(
|
fan_speed_ratio: Optional[float] = betterproto.double_field(1, optional=True)
|
||||||
1, optional=True, group="_fan_speed_ratio"
|
|
||||||
)
|
|
||||||
"""
|
"""
|
||||||
User defined fan speed expressed as a ratio between 0.0 and 1.0 where 0.0
|
User defined fan speed expressed as a ratio between 0.0 and 1.0
|
||||||
means completely turned off and 1.0 means running at full speed possible
|
where 0.0 means completely turned off and
|
||||||
|
1.0 means running at full speed possible
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
class CoolingConfiguration(betterproto.Message):
|
class CoolingConfiguration(betterproto.Message):
|
||||||
minimum_required_fans: Optional[int] = betterproto.uint32_field(
|
minimum_required_fans: Optional[int] = betterproto.uint32_field(1, optional=True)
|
||||||
1, optional=True, group="_minimum_required_fans"
|
|
||||||
)
|
|
||||||
auto: "CoolingAutoMode" = betterproto.message_field(2, group="mode")
|
auto: "CoolingAutoMode" = betterproto.message_field(2, group="mode")
|
||||||
manual: "CoolingManualMode" = betterproto.message_field(3, group="mode")
|
manual: "CoolingManualMode" = betterproto.message_field(3, group="mode")
|
||||||
disabled: "CoolingDisabledMode" = betterproto.message_field(4, group="mode")
|
disabled: "CoolingDisabledMode" = betterproto.message_field(4, group="mode")
|
||||||
@@ -488,23 +491,19 @@ class CoolingConstraints(betterproto.Message):
|
|||||||
class FanState(betterproto.Message):
|
class FanState(betterproto.Message):
|
||||||
"""Structure which contain info about one specific miner fan."""
|
"""Structure which contain info about one specific miner fan."""
|
||||||
|
|
||||||
position: Optional[int] = betterproto.uint32_field(
|
position: Optional[int] = betterproto.uint32_field(1, optional=True)
|
||||||
1, optional=True, group="_position"
|
|
||||||
)
|
|
||||||
"""Fan positions/ID"""
|
"""Fan positions/ID"""
|
||||||
|
|
||||||
rpm: int = betterproto.uint32_field(2)
|
rpm: int = betterproto.uint32_field(2)
|
||||||
"""Actual fan RPM (Revolutions/Rotation Per Minute)"""
|
"""Actual fan RPM (Revolutions/Rotation Per Minute)"""
|
||||||
|
|
||||||
target_speed_ratio: Optional[float] = betterproto.double_field(
|
target_speed_ratio: Optional[float] = betterproto.double_field(3, optional=True)
|
||||||
3, optional=True, group="_target_speed_ratio"
|
|
||||||
)
|
|
||||||
"""Actual fan speed ratio(PWM) in range 0.0 - 1.0"""
|
"""Actual fan speed ratio(PWM) in range 0.0 - 1.0"""
|
||||||
|
|
||||||
|
|
||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
class TemperatureSensor(betterproto.Message):
|
class TemperatureSensor(betterproto.Message):
|
||||||
id: Optional[int] = betterproto.uint32_field(1, optional=True, group="_id")
|
id: Optional[int] = betterproto.uint32_field(1, optional=True)
|
||||||
"""Sensor id"""
|
"""Sensor id"""
|
||||||
|
|
||||||
location: "SensorLocation" = betterproto.enum_field(2)
|
location: "SensorLocation" = betterproto.enum_field(2)
|
||||||
@@ -523,7 +522,10 @@ class GetCoolingStateRequest(betterproto.Message):
|
|||||||
|
|
||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
class GetCoolingStateResponse(betterproto.Message):
|
class GetCoolingStateResponse(betterproto.Message):
|
||||||
"""Response to get current fan states and temperature measurements"""
|
"""
|
||||||
|
Response to get current fan states and
|
||||||
|
temperature measurements
|
||||||
|
"""
|
||||||
|
|
||||||
fans: List["FanState"] = betterproto.message_field(1)
|
fans: List["FanState"] = betterproto.message_field(1)
|
||||||
"""All Fans state"""
|
"""All Fans state"""
|
||||||
@@ -551,12 +553,10 @@ class SetImmersionModeResponse(betterproto.Message):
|
|||||||
|
|
||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
class TunerConfiguration(betterproto.Message):
|
class TunerConfiguration(betterproto.Message):
|
||||||
enabled: Optional[bool] = betterproto.bool_field(1, optional=True, group="_enabled")
|
enabled: Optional[bool] = betterproto.bool_field(1, optional=True)
|
||||||
"""Flag if tuner is enabled"""
|
"""Flag if tuner is enabled"""
|
||||||
|
|
||||||
tuner_mode: Optional["TunerMode"] = betterproto.enum_field(
|
tuner_mode: Optional["TunerMode"] = betterproto.enum_field(2, optional=True)
|
||||||
2, optional=True, group="_tuner_mode"
|
|
||||||
)
|
|
||||||
"""Tuner mode"""
|
"""Tuner mode"""
|
||||||
|
|
||||||
power_target: "Power" = betterproto.message_field(3)
|
power_target: "Power" = betterproto.message_field(3)
|
||||||
@@ -583,7 +583,7 @@ class TunerConstraints(betterproto.Message):
|
|||||||
|
|
||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
class DpsConfiguration(betterproto.Message):
|
class DpsConfiguration(betterproto.Message):
|
||||||
enabled: Optional[bool] = betterproto.bool_field(1, optional=True, group="_enabled")
|
enabled: Optional[bool] = betterproto.bool_field(1, optional=True)
|
||||||
"""Flag if Dynamic Performance Scaling is enabled"""
|
"""Flag if Dynamic Performance Scaling is enabled"""
|
||||||
|
|
||||||
power_step: "Power" = betterproto.message_field(2)
|
power_step: "Power" = betterproto.message_field(2)
|
||||||
@@ -598,9 +598,7 @@ class DpsConfiguration(betterproto.Message):
|
|||||||
min_hashrate_target: "TeraHashrate" = betterproto.message_field(5)
|
min_hashrate_target: "TeraHashrate" = betterproto.message_field(5)
|
||||||
"""Dynamic Performance Scaling minimal hashrate target"""
|
"""Dynamic Performance Scaling minimal hashrate target"""
|
||||||
|
|
||||||
shutdown_enabled: Optional[bool] = betterproto.bool_field(
|
shutdown_enabled: Optional[bool] = betterproto.bool_field(6, optional=True)
|
||||||
6, optional=True, group="_shutdown_enabled"
|
|
||||||
)
|
|
||||||
"""Flag if shutdown for Dynamic Performance Scaling is enabled"""
|
"""Flag if shutdown for Dynamic Performance Scaling is enabled"""
|
||||||
|
|
||||||
shutdown_duration: "Hours" = betterproto.message_field(7)
|
shutdown_duration: "Hours" = betterproto.message_field(7)
|
||||||
@@ -879,17 +877,13 @@ class SetDpsRequest(betterproto.Message):
|
|||||||
save_action: "SaveAction" = betterproto.enum_field(1)
|
save_action: "SaveAction" = betterproto.enum_field(1)
|
||||||
"""Save action"""
|
"""Save action"""
|
||||||
|
|
||||||
enable: Optional[bool] = betterproto.bool_field(2, optional=True, group="_enable")
|
enable: Optional[bool] = betterproto.bool_field(2, optional=True)
|
||||||
"""Flag if Dynamic Performance Scaling should be enabled"""
|
"""Flag if Dynamic Performance Scaling should be enabled"""
|
||||||
|
|
||||||
enable_shutdown: Optional[bool] = betterproto.bool_field(
|
enable_shutdown: Optional[bool] = betterproto.bool_field(3, optional=True)
|
||||||
3, optional=True, group="_enable_shutdown"
|
|
||||||
)
|
|
||||||
"""Flag if shutdown for Dynamic Performance Scaling should be enabled"""
|
"""Flag if shutdown for Dynamic Performance Scaling should be enabled"""
|
||||||
|
|
||||||
shutdown_duration: Optional["Hours"] = betterproto.message_field(
|
shutdown_duration: Optional["Hours"] = betterproto.message_field(4, optional=True)
|
||||||
4, optional=True, group="_shutdown_duration"
|
|
||||||
)
|
|
||||||
"""Dynamic Performance Scaling shutdown duration"""
|
"""Dynamic Performance Scaling shutdown duration"""
|
||||||
|
|
||||||
target: "DpsTarget" = betterproto.message_field(5)
|
target: "DpsTarget" = betterproto.message_field(5)
|
||||||
@@ -898,17 +892,13 @@ class SetDpsRequest(betterproto.Message):
|
|||||||
|
|
||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
class SetDpsResponse(betterproto.Message):
|
class SetDpsResponse(betterproto.Message):
|
||||||
enabled: Optional[bool] = betterproto.bool_field(1, optional=True, group="_enabled")
|
enabled: Optional[bool] = betterproto.bool_field(1, optional=True)
|
||||||
"""Flag if Dynamic Performance Scaling is enabled"""
|
"""Flag if Dynamic Performance Scaling is enabled"""
|
||||||
|
|
||||||
shutdown_enabled: Optional[bool] = betterproto.bool_field(
|
shutdown_enabled: Optional[bool] = betterproto.bool_field(2, optional=True)
|
||||||
2, optional=True, group="_shutdown_enabled"
|
|
||||||
)
|
|
||||||
"""Flag if shutdown for Dynamic Performance Scaling should be enabled"""
|
"""Flag if shutdown for Dynamic Performance Scaling should be enabled"""
|
||||||
|
|
||||||
shutdown_duration: Optional["Hours"] = betterproto.message_field(
|
shutdown_duration: Optional["Hours"] = betterproto.message_field(3, optional=True)
|
||||||
3, optional=True, group="_shutdown_duration"
|
|
||||||
)
|
|
||||||
"""Dynamic Performance Scaling shutdown duration"""
|
"""Dynamic Performance Scaling shutdown duration"""
|
||||||
|
|
||||||
power_target: "DpsPowerTarget" = betterproto.message_field(4)
|
power_target: "DpsPowerTarget" = betterproto.message_field(4)
|
||||||
@@ -935,7 +925,7 @@ class HashboardConfig(betterproto.Message):
|
|||||||
id: str = betterproto.string_field(1)
|
id: str = betterproto.string_field(1)
|
||||||
"""Hashboard id"""
|
"""Hashboard id"""
|
||||||
|
|
||||||
enabled: Optional[bool] = betterproto.bool_field(2, optional=True, group="_enabled")
|
enabled: Optional[bool] = betterproto.bool_field(2, optional=True)
|
||||||
"""Flag if HB si enabled"""
|
"""Flag if HB si enabled"""
|
||||||
|
|
||||||
frequency: "Frequency" = betterproto.message_field(3)
|
frequency: "Frequency" = betterproto.message_field(3)
|
||||||
@@ -1019,9 +1009,9 @@ class Quota(betterproto.Message):
|
|||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
class FixedShareRatio(betterproto.Message):
|
class FixedShareRatio(betterproto.Message):
|
||||||
"""
|
"""
|
||||||
Structure for fixed share ratio load balance strategy Fixed share ratio is
|
Structure for fixed share ratio load balance strategy
|
||||||
value between 0.0 to 1.0 where 1.0 represents that all work is generated
|
Fixed share ratio is value between 0.0 to 1.0 where 1.0 represents that all work is
|
||||||
from the group
|
generated from the group
|
||||||
"""
|
"""
|
||||||
|
|
||||||
value: float = betterproto.double_field(1)
|
value: float = betterproto.double_field(1)
|
||||||
@@ -1058,12 +1048,10 @@ class PoolConfiguration(betterproto.Message):
|
|||||||
user: str = betterproto.string_field(3)
|
user: str = betterproto.string_field(3)
|
||||||
"""Pool connection user"""
|
"""Pool connection user"""
|
||||||
|
|
||||||
password: Optional[str] = betterproto.string_field(
|
password: Optional[str] = betterproto.string_field(4, optional=True)
|
||||||
4, optional=True, group="_password"
|
|
||||||
)
|
|
||||||
"""Pool connection password if set"""
|
"""Pool connection password if set"""
|
||||||
|
|
||||||
enabled: Optional[bool] = betterproto.bool_field(5, optional=True, group="_enabled")
|
enabled: Optional[bool] = betterproto.bool_field(5, optional=True)
|
||||||
"""Flag if pool connection is enabled"""
|
"""Flag if pool connection is enabled"""
|
||||||
|
|
||||||
|
|
||||||
@@ -1130,9 +1118,7 @@ class PoolStats(betterproto.Message):
|
|||||||
generated_work: int = betterproto.uint64_field(6)
|
generated_work: int = betterproto.uint64_field(6)
|
||||||
"""Generated work"""
|
"""Generated work"""
|
||||||
|
|
||||||
last_share_time: Optional[datetime] = betterproto.message_field(
|
last_share_time: Optional[datetime] = betterproto.message_field(7, optional=True)
|
||||||
7, optional=True, group="_last_share_time"
|
|
||||||
)
|
|
||||||
"""Last share time"""
|
"""Last share time"""
|
||||||
|
|
||||||
|
|
||||||
@@ -1154,9 +1140,9 @@ class GetPoolGroupsResponse(betterproto.Message):
|
|||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
class CreatePoolGroupRequest(betterproto.Message):
|
class CreatePoolGroupRequest(betterproto.Message):
|
||||||
"""
|
"""
|
||||||
Request for pool group create action group.uid must not be specified (it
|
Request for pool group create action
|
||||||
will be generated) group.pools[].uid must not be specified (it will be
|
group.uid must not be specified (it will be generated)
|
||||||
generated)
|
group.pools[].uid must not be specified (it will be generated)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
save_action: "SaveAction" = betterproto.enum_field(1)
|
save_action: "SaveAction" = betterproto.enum_field(1)
|
||||||
@@ -1177,9 +1163,9 @@ class CreatePoolGroupResponse(betterproto.Message):
|
|||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
class UpdatePoolGroupRequest(betterproto.Message):
|
class UpdatePoolGroupRequest(betterproto.Message):
|
||||||
"""
|
"""
|
||||||
Request for pool group update action group.uid must be specified and
|
Request for pool group update action
|
||||||
represents unique id of group which will be updated group.pools[].uid must
|
group.uid must be specified and represents unique id of group which will be updated
|
||||||
not be specified (it will be generated)
|
group.pools[].uid must not be specified (it will be generated)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
save_action: "SaveAction" = betterproto.enum_field(1)
|
save_action: "SaveAction" = betterproto.enum_field(1)
|
||||||
@@ -1221,16 +1207,15 @@ class SetPoolGroupsRequest(betterproto.Message):
|
|||||||
|
|
||||||
save_action: "SaveAction" = betterproto.enum_field(1)
|
save_action: "SaveAction" = betterproto.enum_field(1)
|
||||||
"""
|
"""
|
||||||
Save action SAVE just update config but changes will not be applied
|
Save action
|
||||||
SAVE_AND_APPLY and SAVE_AND_FORCE_APPLY are equal for this method. Pools
|
SAVE just update config but changes will not be applied
|
||||||
config will be updated and changes will be applied that will trigger
|
SAVE_AND_APPLY and SAVE_AND_FORCE_APPLY are equal for this method. Pools config will be updated and changes will be applied that will trigger restart.
|
||||||
restart.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pool_groups: List["PoolGroupConfiguration"] = betterproto.message_field(2)
|
pool_groups: List["PoolGroupConfiguration"] = betterproto.message_field(2)
|
||||||
"""
|
"""
|
||||||
Pool groups configuration `uid` must not be specified (it will be
|
Pool groups configuration
|
||||||
generated)
|
`uid` must not be specified (it will be generated)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -1292,9 +1277,8 @@ class NoneLicense(betterproto.Message):
|
|||||||
time_to_restricted: int = betterproto.uint32_field(1)
|
time_to_restricted: int = betterproto.uint32_field(1)
|
||||||
"""
|
"""
|
||||||
BOS Initialization timeout - number of seconds elapsed since bosminer start
|
BOS Initialization timeout - number of seconds elapsed since bosminer start
|
||||||
i.e., number of seconds BOS will start mining in restricted mode burning 5%
|
i.e., number of seconds BOS will start mining in restricted mode burning 5% of hashrate
|
||||||
of hashrate For more, see Section 3.10 of
|
For more, see Section 3.10 of https://braiins.com/os/plus/license
|
||||||
https://braiins.com/os/plus/license
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -1313,9 +1297,8 @@ class ValidLicense(betterproto.Message):
|
|||||||
|
|
||||||
time_to_restricted: int = betterproto.uint32_field(3)
|
time_to_restricted: int = betterproto.uint32_field(3)
|
||||||
"""
|
"""
|
||||||
Current license expiration - number of seconds since the moment the license
|
Current license expiration - number of seconds since the moment the license was received
|
||||||
was received i.e., number of seconds BOS will start mining in restricted
|
i.e., number of seconds BOS will start mining in restricted mode burning 15% of hashrate
|
||||||
mode burning 15% of hashrate
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dev_fee: "BasesPoints" = betterproto.message_field(4)
|
dev_fee: "BasesPoints" = betterproto.message_field(4)
|
||||||
@@ -1388,7 +1371,8 @@ class MinerIdentity(betterproto.Message):
|
|||||||
brand: "MinerBrand" = betterproto.enum_field(1)
|
brand: "MinerBrand" = betterproto.enum_field(1)
|
||||||
model: "MinerModel" = betterproto.enum_field(2)
|
model: "MinerModel" = betterproto.enum_field(2)
|
||||||
"""
|
"""
|
||||||
Deprecated: Use miner_model instead. This field is no longer supported.
|
Deprecated: Use miner_model instead.
|
||||||
|
This field is no longer supported.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = betterproto.string_field(3)
|
name: str = betterproto.string_field(3)
|
||||||
@@ -1564,7 +1548,7 @@ class Hashboard(betterproto.Message):
|
|||||||
stats: "WorkSolverStats" = betterproto.message_field(8)
|
stats: "WorkSolverStats" = betterproto.message_field(8)
|
||||||
"""Hashboard stats"""
|
"""Hashboard stats"""
|
||||||
|
|
||||||
model: Optional[str] = betterproto.string_field(9, optional=True, group="_model")
|
model: Optional[str] = betterproto.string_field(9, optional=True)
|
||||||
"""Hashboard model"""
|
"""Hashboard model"""
|
||||||
|
|
||||||
|
|
||||||
@@ -1644,9 +1628,7 @@ class GetNetworkConfigurationResponse(betterproto.Message):
|
|||||||
class SetNetworkConfigurationRequest(betterproto.Message):
|
class SetNetworkConfigurationRequest(betterproto.Message):
|
||||||
dhcp: "Dhcp" = betterproto.message_field(1, group="protocol")
|
dhcp: "Dhcp" = betterproto.message_field(1, group="protocol")
|
||||||
static: "Static" = betterproto.message_field(2, group="protocol")
|
static: "Static" = betterproto.message_field(2, group="protocol")
|
||||||
hostname: Optional[str] = betterproto.string_field(
|
hostname: Optional[str] = betterproto.string_field(3, optional=True)
|
||||||
3, optional=True, group="_hostname"
|
|
||||||
)
|
|
||||||
"""Hostname. Existing value will be preserved if this field is not set."""
|
"""Hostname. Existing value will be preserved if this field is not set."""
|
||||||
|
|
||||||
|
|
||||||
@@ -1691,27 +1673,21 @@ class GetNetworkInfoRequest(betterproto.Message):
|
|||||||
@dataclass(eq=False, repr=False)
|
@dataclass(eq=False, repr=False)
|
||||||
class GetNetworkInfoResponse(betterproto.Message):
|
class GetNetworkInfoResponse(betterproto.Message):
|
||||||
"""
|
"""
|
||||||
Response message for GetCurrentNetworkConfiguration Represents the current
|
Response message for GetCurrentNetworkConfiguration
|
||||||
network configuration for the default network interface. Only IPv4 is
|
Represents the current network configuration for the default network interface.
|
||||||
supported.
|
Only IPv4 is supported.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = betterproto.string_field(1)
|
name: str = betterproto.string_field(1)
|
||||||
"""Name of the network interface"""
|
"""Name of the network interface"""
|
||||||
|
|
||||||
mac_address: Optional[str] = betterproto.string_field(
|
mac_address: Optional[str] = betterproto.string_field(2, optional=True)
|
||||||
2, optional=True, group="_mac_address"
|
|
||||||
)
|
|
||||||
"""MAC address of the network interface"""
|
"""MAC address of the network interface"""
|
||||||
|
|
||||||
hostname: Optional[str] = betterproto.string_field(
|
hostname: Optional[str] = betterproto.string_field(3, optional=True)
|
||||||
3, optional=True, group="_hostname"
|
|
||||||
)
|
|
||||||
"""Miner hostname"""
|
"""Miner hostname"""
|
||||||
|
|
||||||
protocol: Optional["NetworkProtocol"] = betterproto.enum_field(
|
protocol: Optional["NetworkProtocol"] = betterproto.enum_field(4, optional=True)
|
||||||
4, optional=True, group="_protocol"
|
|
||||||
)
|
|
||||||
"""Network protocol"""
|
"""Network protocol"""
|
||||||
|
|
||||||
dns_servers: List[str] = betterproto.string_field(5)
|
dns_servers: List[str] = betterproto.string_field(5)
|
||||||
@@ -1720,9 +1696,7 @@ class GetNetworkInfoResponse(betterproto.Message):
|
|||||||
networks: List["IpNetwork"] = betterproto.message_field(6)
|
networks: List["IpNetwork"] = betterproto.message_field(6)
|
||||||
"""List of assigned IP addresses"""
|
"""List of assigned IP addresses"""
|
||||||
|
|
||||||
default_gateway: Optional[str] = betterproto.string_field(
|
default_gateway: Optional[str] = betterproto.string_field(7, optional=True)
|
||||||
7, optional=True, group="_default_gateway"
|
|
||||||
)
|
|
||||||
"""Default gateway/route for the interface"""
|
"""Default gateway/route for the interface"""
|
||||||
|
|
||||||
|
|
||||||
@@ -2332,7 +2306,7 @@ class MinerServiceStub(betterproto.ServiceStub):
|
|||||||
timeout: Optional[float] = None,
|
timeout: Optional[float] = None,
|
||||||
deadline: Optional["Deadline"] = None,
|
deadline: Optional["Deadline"] = None,
|
||||||
metadata: Optional["MetadataLike"] = None
|
metadata: Optional["MetadataLike"] = None
|
||||||
) -> AsyncIterator["GetMinerStatusResponse"]:
|
) -> AsyncIterator[GetMinerStatusResponse]:
|
||||||
async for response in self._unary_stream(
|
async for response in self._unary_stream(
|
||||||
"/braiins.bos.v1.MinerService/GetMinerStatus",
|
"/braiins.bos.v1.MinerService/GetMinerStatus",
|
||||||
get_miner_status_request,
|
get_miner_status_request,
|
||||||
@@ -2418,7 +2392,7 @@ class MinerServiceStub(betterproto.ServiceStub):
|
|||||||
timeout: Optional[float] = None,
|
timeout: Optional[float] = None,
|
||||||
deadline: Optional["Deadline"] = None,
|
deadline: Optional["Deadline"] = None,
|
||||||
metadata: Optional["MetadataLike"] = None
|
metadata: Optional["MetadataLike"] = None
|
||||||
) -> AsyncIterator["GetSupportArchiveResponse"]:
|
) -> AsyncIterator[GetSupportArchiveResponse]:
|
||||||
async for response in self._unary_stream(
|
async for response in self._unary_stream(
|
||||||
"/braiins.bos.v1.MinerService/GetSupportArchive",
|
"/braiins.bos.v1.MinerService/GetSupportArchive",
|
||||||
get_support_archive_request,
|
get_support_archive_request,
|
||||||
@@ -3195,7 +3169,7 @@ class MinerServiceBase(ServiceBase):
|
|||||||
|
|
||||||
async def get_miner_status(
|
async def get_miner_status(
|
||||||
self, get_miner_status_request: "GetMinerStatusRequest"
|
self, get_miner_status_request: "GetMinerStatusRequest"
|
||||||
) -> AsyncIterator["GetMinerStatusResponse"]:
|
) -> AsyncIterator[GetMinerStatusResponse]:
|
||||||
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
||||||
yield GetMinerStatusResponse()
|
yield GetMinerStatusResponse()
|
||||||
|
|
||||||
@@ -3221,7 +3195,7 @@ class MinerServiceBase(ServiceBase):
|
|||||||
|
|
||||||
async def get_support_archive(
|
async def get_support_archive(
|
||||||
self, get_support_archive_request: "GetSupportArchiveRequest"
|
self, get_support_archive_request: "GetSupportArchiveRequest"
|
||||||
) -> AsyncIterator["GetSupportArchiveResponse"]:
|
) -> AsyncIterator[GetSupportArchiveResponse]:
|
||||||
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
||||||
yield GetSupportArchiveResponse()
|
yield GetSupportArchiveResponse()
|
||||||
|
|
||||||
|
|||||||
77
pyasic/web/iceriver.py
Normal file
77
pyasic/web/iceriver.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2024 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import warnings
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from pyasic import settings
|
||||||
|
from pyasic.errors import APIError
|
||||||
|
from pyasic.web.base import BaseWebAPI
|
||||||
|
|
||||||
|
|
||||||
|
class IceRiverWebAPI(BaseWebAPI):
|
||||||
|
def __init__(self, ip: str) -> None:
|
||||||
|
super().__init__(ip)
|
||||||
|
self.username = "admin"
|
||||||
|
self.pwd = settings.get("default_iceriver_web_password", "12345678")
|
||||||
|
|
||||||
|
async def multicommand(
|
||||||
|
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||||
|
) -> dict:
|
||||||
|
tasks = {c: asyncio.create_task(getattr(self, c)()) for c in commands}
|
||||||
|
await asyncio.gather(*[t for t in tasks.values()])
|
||||||
|
return {t: tasks[t].result() for t in tasks}
|
||||||
|
|
||||||
|
async def send_command(
|
||||||
|
self,
|
||||||
|
command: str | bytes,
|
||||||
|
ignore_errors: bool = False,
|
||||||
|
allow_warning: bool = True,
|
||||||
|
privileged: bool = False,
|
||||||
|
**parameters: Any,
|
||||||
|
) -> dict:
|
||||||
|
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||||
|
try:
|
||||||
|
# auth
|
||||||
|
await client.post(
|
||||||
|
f"http://{self.ip}:{self.port}/user/loginpost",
|
||||||
|
params={"post": "6", "user": self.username, "pwd": self.pwd},
|
||||||
|
)
|
||||||
|
except httpx.HTTPError:
|
||||||
|
warnings.warn(f"Could not authenticate with miner web: {self}")
|
||||||
|
try:
|
||||||
|
resp = await client.post(
|
||||||
|
f"http://{self.ip}:{self.port}/user/{command}", params=parameters
|
||||||
|
)
|
||||||
|
if not resp.status_code == 200:
|
||||||
|
if not ignore_errors:
|
||||||
|
raise APIError(f"Command failed: {command}")
|
||||||
|
warnings.warn(f"Command failed: {command}")
|
||||||
|
return resp.json()
|
||||||
|
except httpx.HTTPError:
|
||||||
|
raise APIError(f"Command failed: {command}")
|
||||||
|
|
||||||
|
async def locate(self, enable: bool):
|
||||||
|
return await self.send_command(
|
||||||
|
"userpanel", post="5", locate="1" if enable else "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def userpanel(self):
|
||||||
|
return await self.send_command("userpanel", post="4")
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pyasic"
|
name = "pyasic"
|
||||||
version = "0.59.5"
|
version = "0.61.3"
|
||||||
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"
|
||||||
@@ -8,21 +8,21 @@ documentation = "https://pyasic.readthedocs.io/en/latest/"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.8"
|
python = "^3.9"
|
||||||
httpx = ">=0.26.0"
|
httpx = ">=0.26.0"
|
||||||
asyncssh = ">=2.14.2"
|
asyncssh = ">=2.17.0"
|
||||||
passlib = ">=1.7.4"
|
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.0b6"
|
betterproto = "2.0.0b7"
|
||||||
aiofiles = ">=23.2.1"
|
aiofiles = ">=23.2.1"
|
||||||
|
|
||||||
[tool.poetry.group.dev]
|
[tool.poetry.group.dev]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pre-commit = "^3.5.0"
|
pre-commit = "^4.0.1"
|
||||||
isort = "^5.12.0"
|
isort = "^5.12.0"
|
||||||
|
|
||||||
[tool.poetry.group.docs]
|
[tool.poetry.group.docs]
|
||||||
|
|||||||
Reference in New Issue
Block a user