Compare commits
126 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7365275f46 | ||
|
|
0ecab5fdd4 | ||
|
|
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 | ||
|
|
d0b9dff476 | ||
|
|
501e290839 | ||
|
|
a0daf37f80 | ||
|
|
8111b1ff4b | ||
|
|
754087afd6 | ||
|
|
5e16b6092c | ||
|
|
21636a75fa | ||
|
|
f124f5422a | ||
|
|
1e5d1a2528 | ||
|
|
1fcef07902 | ||
|
|
41e7dd8056 | ||
|
|
dccc35db5f | ||
|
|
0cfe59aa34 | ||
|
|
6fdd156fa3 | ||
|
|
e9fcf25ad3 | ||
|
|
a9422165ca | ||
|
|
0ea5ee8239 | ||
|
|
fba25cba61 | ||
|
|
343b5a1c50 | ||
|
|
63522aad81 | ||
|
|
b957aa7fba | ||
|
|
a71aa6868a | ||
|
|
6b50bf0cf7 | ||
|
|
d00444ec56 | ||
|
|
e7ed39fe39 | ||
|
|
168d68d0b2 | ||
|
|
63cddfdde3 | ||
|
|
4a642fd3da | ||
|
|
13c0407b2d | ||
|
|
794ed6d103 | ||
|
|
d0aeb5a6ce | ||
|
|
030f8c6079 | ||
|
|
7195e204ce | ||
|
|
962a328219 | ||
|
|
1cec2ca7f3 | ||
|
|
a3c4187411 | ||
|
|
18a2df5d9b | ||
|
|
6d66c793cb | ||
|
|
b434c8df1a | ||
|
|
2b8fa2fc2b | ||
|
|
1497d2abea | ||
|
|
a2ca79843d | ||
|
|
f6500e7d66 | ||
|
|
ea2fd0fc9a | ||
|
|
e2cbd30a99 | ||
|
|
151ea44b10 | ||
|
|
6487a0b08e | ||
|
|
552fdf9ec0 | ||
|
|
00cf1449f9 | ||
|
|
8ec88e385a | ||
|
|
cc29b2960a | ||
|
|
568ffd67c4 | ||
|
|
4b4670201a | ||
|
|
92f70c9a76 | ||
|
|
1d2dc3fddf | ||
|
|
c44150fd15 | ||
|
|
8664b53991 | ||
|
|
31aeca2340 | ||
|
|
34eec3ff2e | ||
|
|
e1416b5a4b | ||
|
|
3ca75729b9 | ||
|
|
73031eea65 | ||
|
|
1643c5b7ee | ||
|
|
ca5db726bd | ||
|
|
4bb4d32b48 | ||
|
|
fec7a89807 | ||
|
|
db2615a4eb | ||
|
|
eea5d5ba2a | ||
|
|
f405bbff4d | ||
|
|
dd8d895b50 | ||
|
|
dff4e98523 | ||
|
|
846bbb9033 | ||
|
|
e6f9a33b3c | ||
|
|
092126bded | ||
|
|
ae3d38603a | ||
|
|
e649348af2 | ||
|
|
ba58e80ec3 | ||
|
|
45e2c9a403 | ||
|
|
bd9592c19c | ||
|
|
1bb597999d | ||
|
|
7803fa60f2 | ||
|
|
4adb7dc92c | ||
|
|
ba69a1de2c | ||
|
|
64265206c2 | ||
|
|
eec8f66b81 | ||
|
|
999e8ef318 | ||
|
|
eefb055a3f | ||
|
|
9c41a6b28f | ||
|
|
bf0e2e6cfe | ||
|
|
4a2adabe95 |
@@ -255,7 +255,7 @@ if __name__ == "__main__":
|
||||
```python
|
||||
from pyasic import settings
|
||||
|
||||
settings.update("default_antminer_password", "my_pwd")
|
||||
settings.update("default_antminer_web_password", "my_pwd")
|
||||
```
|
||||
|
||||
##### Default values:
|
||||
|
||||
@@ -51,6 +51,8 @@ def backend_str(backend: MinerTypes) -> str:
|
||||
return "Mara Firmware Miners"
|
||||
case MinerTypes.BITAXE:
|
||||
return "Stock Firmware BitAxe Miners"
|
||||
case MinerTypes.ICERIVER:
|
||||
return "Stock Firmware IceRiver Miners"
|
||||
|
||||
|
||||
def create_url_str(mtype: str):
|
||||
|
||||
@@ -249,7 +249,7 @@ if __name__ == "__main__":
|
||||
```python
|
||||
from pyasic import settings
|
||||
|
||||
settings.update("default_antminer_password", "my_pwd")
|
||||
settings.update("default_antminer_web_password", "my_pwd")
|
||||
```
|
||||
|
||||
##### Default values:
|
||||
|
||||
@@ -225,6 +225,13 @@
|
||||
show_root_heading: false
|
||||
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+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
|
||||
handler: python
|
||||
@@ -281,6 +288,13 @@
|
||||
show_root_heading: false
|
||||
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)
|
||||
::: pyasic.miners.antminer.vnish.X19.T19.VNishT19
|
||||
handler: python
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
show_root_heading: false
|
||||
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)
|
||||
::: pyasic.miners.antminer.bmminer.X21.T21.BMMinerT21
|
||||
handler: python
|
||||
@@ -36,6 +43,13 @@
|
||||
show_root_heading: false
|
||||
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)
|
||||
::: pyasic.miners.antminer.epic.X21.T21.ePICT21
|
||||
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>
|
||||
<ul>
|
||||
<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>
|
||||
</ul>
|
||||
</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#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>
|
||||
</ul>
|
||||
</details>
|
||||
@@ -505,6 +507,7 @@ details {
|
||||
<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-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>
|
||||
</ul>
|
||||
</details>
|
||||
@@ -535,6 +538,7 @@ details {
|
||||
<summary>X21 Series:</summary>
|
||||
<ul>
|
||||
<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>
|
||||
</ul>
|
||||
</details>
|
||||
@@ -650,4 +654,15 @@ details {
|
||||
</ul>
|
||||
</details>
|
||||
</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>
|
||||
@@ -1,3 +1,4 @@
|
||||
jinja2<3.1.4
|
||||
mkdocs
|
||||
mkdocstrings[python]
|
||||
zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||
|
||||
@@ -13,13 +13,17 @@ Settings options:
|
||||
- `get_data_retries`
|
||||
- `api_function_timeout`
|
||||
- `antminer_mining_mode_as_str`
|
||||
- `default_whatsminer_password`
|
||||
- `default_innosilicon_password`
|
||||
- `default_antminer_password`
|
||||
- `default_bosminer_password`
|
||||
- `default_vnish_password`
|
||||
- `default_goldshell_password`
|
||||
- `socket_linger_time`
|
||||
- `default_whatsminer_rpc_password`
|
||||
- `default_innosilicon_web_password`
|
||||
- `default_antminer_web_password`
|
||||
- `default_bosminer_web_password`
|
||||
- `default_vnish_web_password`
|
||||
- `default_goldshell_web_password`
|
||||
- `default_auradine_web_password`
|
||||
- `default_epic_web_password`
|
||||
- `default_hive_web_password`
|
||||
- `default_antminer_ssh_password`
|
||||
- `default_bosminer_ssh_password`
|
||||
|
||||
|
||||
### get
|
||||
|
||||
785
poetry.lock
generated
785
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -593,6 +593,8 @@ class MiningModeConfig(MinerConfigOption):
|
||||
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="hashrate"),
|
||||
)
|
||||
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_auradine(cls, web_mode: dict):
|
||||
try:
|
||||
|
||||
@@ -21,6 +21,13 @@ from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
||||
PoolConfiguration,
|
||||
PoolGroupConfiguration,
|
||||
Quota,
|
||||
SaveAction,
|
||||
SetPoolGroupsRequest,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -134,6 +141,11 @@ class Pool(MinerConfigValue):
|
||||
"stratumPassword": self.password,
|
||||
}
|
||||
|
||||
def as_boser(self) -> PoolConfiguration:
|
||||
return PoolConfiguration(
|
||||
url=self.url, user=self.user, password=self.password, enabled=True
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "Pool":
|
||||
return cls(
|
||||
@@ -306,6 +318,13 @@ class PoolGroup(MinerConfigValue):
|
||||
def as_bitaxe(self, user_suffix: str = None) -> dict:
|
||||
return self.pools[0].as_bitaxe(user_suffix=user_suffix)
|
||||
|
||||
def as_boser(self, user_suffix: str = None) -> PoolGroupConfiguration:
|
||||
return PoolGroupConfiguration(
|
||||
name=self.name,
|
||||
quota=Quota(value=self.quota),
|
||||
pools=[p.as_boser() for p in self.pools],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "PoolGroup":
|
||||
cls_conf = {}
|
||||
@@ -446,7 +465,12 @@ class PoolConfig(MinerConfigValue):
|
||||
return {"group": [PoolGroup().as_bosminer()]}
|
||||
|
||||
def as_boser(self, user_suffix: str = None) -> dict:
|
||||
return {}
|
||||
return {
|
||||
"set_pool_groups": SetPoolGroupsRequest(
|
||||
save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
||||
pool_groups=[g.as_boser(user_suffix=user_suffix) for g in self.groups],
|
||||
)
|
||||
}
|
||||
|
||||
def as_auradine(self, user_suffix: str = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
class Scheme (Enum):
|
||||
class Scheme(Enum):
|
||||
STRATUM_V1 = "stratum+tcp"
|
||||
STRATUM_V2 = "stratum2+tcp"
|
||||
|
||||
@@ -23,12 +23,12 @@ class PoolUrl:
|
||||
return f"{self.scheme.value}://{self.host}:{self.port}"
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, url: str) -> 'PoolUrl':
|
||||
def from_str(cls, url: str) -> "PoolUrl":
|
||||
parsed_url = urlparse(url)
|
||||
scheme = Scheme(parsed_url.scheme)
|
||||
host = parsed_url.hostname
|
||||
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
|
||||
return cls(scheme=scheme, host=host, port=port, pubkey=pubkey)
|
||||
|
||||
|
||||
@@ -50,8 +50,6 @@ class PoolMetrics:
|
||||
pool_stale_percent: Percentage of stale shares by the pool.
|
||||
"""
|
||||
|
||||
pool_rejected_percent: float = field(init=False)
|
||||
pool_stale_percent: float = field(init=False)
|
||||
url: PoolUrl
|
||||
accepted: int = None
|
||||
rejected: int = None
|
||||
@@ -61,6 +59,8 @@ class PoolMetrics:
|
||||
alive: bool = None
|
||||
index: int = None
|
||||
user: str = None
|
||||
pool_rejected_percent: float = field(init=False)
|
||||
pool_stale_percent: float = field(init=False)
|
||||
|
||||
@property
|
||||
def pool_rejected_percent(self) -> float: # noqa - Skip PyCharm inspection
|
||||
|
||||
@@ -44,6 +44,7 @@ class AntminerModels(str, Enum):
|
||||
S19kProNoPIC = "S19k Pro No PIC"
|
||||
T19 = "T19"
|
||||
S21 = "S21"
|
||||
S21Pro = "S21 Pro"
|
||||
T21 = "T21"
|
||||
|
||||
def __str__(self):
|
||||
@@ -283,6 +284,7 @@ class AvalonminerModels(str, Enum):
|
||||
Avalon1066 = "Avalon 1066"
|
||||
Avalon1166Pro = "Avalon 1166 Pro"
|
||||
Avalon1246 = "Avalon 1246"
|
||||
AvalonNano3 = "Avalon Nano 3"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
@@ -338,6 +340,13 @@ class BitAxeModels(str, Enum):
|
||||
return self.value
|
||||
|
||||
|
||||
class IceRiverModels(str, Enum):
|
||||
KS2 = "KS2"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class MinerModel:
|
||||
ANTMINER = AntminerModels
|
||||
WHATSMINER = WhatsminerModels
|
||||
@@ -347,3 +356,4 @@ class MinerModel:
|
||||
AURADINE = AuradineModels
|
||||
EPIC = ePICModels
|
||||
BITAXE = BitAxeModels
|
||||
ICERIVER = IceRiverModels
|
||||
|
||||
@@ -15,8 +15,12 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AntminerModern
|
||||
from pyasic.miners.device.models import S21
|
||||
from pyasic.miners.device.models import S21, S21Pro
|
||||
|
||||
|
||||
class BMMinerS21(AntminerModern, S21):
|
||||
pass
|
||||
|
||||
|
||||
class BMMinerS21Pro(AntminerModern, S21Pro):
|
||||
pass
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from .S21 import BMMinerS21
|
||||
from .S21 import BMMinerS21, BMMinerS21Pro
|
||||
from .T21 import BMMinerT21
|
||||
|
||||
@@ -29,6 +29,7 @@ from pyasic.miners.device.models import (
|
||||
S19kProNoPIC,
|
||||
S19Plus,
|
||||
S19Pro,
|
||||
S19ProPlusHydro,
|
||||
)
|
||||
|
||||
|
||||
@@ -82,3 +83,7 @@ class BOSMinerS19jProPlusNoPIC(BOSer, S19jProPlusNoPIC):
|
||||
|
||||
class BOSMinerS19XP(BOSer, S19XP):
|
||||
pass
|
||||
|
||||
|
||||
class BOSMinerS19ProPlusHydro(BOSer, S19ProPlusHydro):
|
||||
pass
|
||||
|
||||
@@ -27,6 +27,7 @@ from .S19 import (
|
||||
BOSMinerS19kProNoPIC,
|
||||
BOSMinerS19Plus,
|
||||
BOSMinerS19Pro,
|
||||
BOSMinerS19ProPlusHydro,
|
||||
BOSMinerS19XP,
|
||||
)
|
||||
from .T19 import BOSMinerT19
|
||||
|
||||
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 .T21 import BOSMinerT21
|
||||
|
||||
@@ -15,8 +15,12 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import ePIC
|
||||
from pyasic.miners.device.models import S21
|
||||
from pyasic.miners.device.models import S21, S21Pro
|
||||
|
||||
|
||||
class ePICS21(ePIC, S21):
|
||||
pass
|
||||
|
||||
|
||||
class ePICS21Pro(ePIC, S21Pro):
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .S21 import (
|
||||
ePICS21,
|
||||
)
|
||||
from .S21 import ePICS21, ePICS21Pro
|
||||
|
||||
from .T21 import (
|
||||
ePICT21,
|
||||
|
||||
@@ -24,6 +24,7 @@ from pyasic.miners.device.models import (
|
||||
S19jPro,
|
||||
S19NoPIC,
|
||||
S19Pro,
|
||||
S19ProHydro,
|
||||
)
|
||||
|
||||
|
||||
@@ -57,3 +58,7 @@ class VNishS19j(VNish, S19j):
|
||||
|
||||
class VNishS19jPro(VNish, S19jPro):
|
||||
pass
|
||||
|
||||
|
||||
class VNishS19ProHydro(VNish, S19ProHydro):
|
||||
pass
|
||||
|
||||
@@ -22,6 +22,7 @@ from .S19 import (
|
||||
VNishS19jPro,
|
||||
VNishS19NoPIC,
|
||||
VNishS19Pro,
|
||||
VNishS19ProHydro,
|
||||
VNishS19XP,
|
||||
)
|
||||
from .T19 import VNishT19
|
||||
|
||||
@@ -20,3 +20,4 @@ from .A9X import *
|
||||
from .A10X import *
|
||||
from .A11X 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 .goldshell import GoldshellMiner
|
||||
from .hiveon import Hiveon
|
||||
from .iceriver import IceRiver
|
||||
from .innosilicon import Innosilicon
|
||||
from .luxminer import LUXMiner
|
||||
from .marathon import MaraMiner
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from typing import List, Optional, Union
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
from pyasic.config import MinerConfig, MiningModeConfig
|
||||
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
|
||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||
from pyasic.data.pools import PoolMetrics
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends.bmminer import BMMiner
|
||||
from pyasic.miners.backends.cgminer import CGMiner
|
||||
@@ -124,6 +126,34 @@ class AntminerModern(BMMiner):
|
||||
# break
|
||||
# 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:
|
||||
data = await self.web.blink(blink=True)
|
||||
if data:
|
||||
@@ -370,6 +400,8 @@ class AntminerModern(BMMiner):
|
||||
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"),
|
||||
@@ -377,7 +409,7 @@ class AntminerModern(BMMiner):
|
||||
remote_failures=pool_info.get("Remote Failures"),
|
||||
active=pool_info.get("Stratum Active"),
|
||||
alive=pool_info.get("Status") == "Alive",
|
||||
url=pool_info.get("URL"),
|
||||
url=pool_url,
|
||||
user=pool_info.get("User"),
|
||||
index=pool_info.get("POOL"),
|
||||
)
|
||||
@@ -425,6 +457,10 @@ ANTMINER_OLD_DATA_LOC = DataLocations(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools",
|
||||
[RPCAPICommand("rpc_pools", "pools")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -193,6 +193,40 @@ class Auradine(StockFirmware):
|
||||
for key in conf.keys():
|
||||
await self.web.send_command(command=key, **conf[key])
|
||||
|
||||
async def upgrade_firmware(self, *, url: str = None, version: str = "latest", keep_settings: bool = False, **kwargs) -> bool:
|
||||
"""
|
||||
Upgrade the firmware of the Auradine device.
|
||||
|
||||
Args:
|
||||
url (str): The URL to download the firmware from.
|
||||
version (str): The version of the firmware to upgrade to.
|
||||
keep_settings (bool): Whether to keep the current settings during the upgrade.
|
||||
|
||||
Returns:
|
||||
bool: True if the firmware upgrade was successful, False otherwise.
|
||||
"""
|
||||
try:
|
||||
logging.info("Starting firmware upgrade process.")
|
||||
|
||||
if not url and not version:
|
||||
raise ValueError("Either URL or version must be provided for firmware upgrade.")
|
||||
|
||||
if url:
|
||||
result = await self.web.firmware_upgrade(url=url)
|
||||
else:
|
||||
result = await self.web.firmware_upgrade(version=version)
|
||||
|
||||
if result.get("STATUS", [{}])[0].get("STATUS") == "S":
|
||||
logging.info("Firmware upgrade process completed successfully.")
|
||||
return True
|
||||
else:
|
||||
logging.error(f"Firmware upgrade failed: {result.get('error', 'Unknown error')}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"An error occurred during the firmware upgrade process: {str(e)}")
|
||||
return False
|
||||
|
||||
##################################################
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
|
||||
@@ -68,6 +68,10 @@ AVALON_DATA_LOC = DataLocations(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools",
|
||||
[RPCAPICommand("rpc_pools", "pools")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -114,7 +118,7 @@ class AvalonMiner(CGMiner):
|
||||
stats_items = []
|
||||
stats_dict = {}
|
||||
for item in _stats_items:
|
||||
if ":" in item:
|
||||
if ": " in item:
|
||||
data = item.replace("]", "").split("[")
|
||||
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
|
||||
data_dict = {}
|
||||
@@ -143,10 +147,7 @@ class AvalonMiner(CGMiner):
|
||||
if raw_data[0] == "":
|
||||
raw_data = raw_data[1:]
|
||||
|
||||
if len(raw_data) == 2:
|
||||
stats_dict[raw_data[0]] = raw_data[1]
|
||||
else:
|
||||
stats_dict[raw_data[0]] = raw_data[1:]
|
||||
stats_dict[raw_data[0]] = raw_data[1:]
|
||||
stats_items.append(raw_data)
|
||||
|
||||
return stats_dict
|
||||
@@ -216,7 +217,7 @@ class AvalonMiner(CGMiner):
|
||||
try:
|
||||
board_hr = parsed_stats["MGHS"][board]
|
||||
hashboards[board].hashrate = AlgoHashRate.SHA256(
|
||||
board_hr, HashUnit.SHA256.GH
|
||||
float(board_hr), HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -252,7 +253,7 @@ class AvalonMiner(CGMiner):
|
||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
return AlgoHashRate.SHA256(
|
||||
parsed_stats["GHSmm"], HashUnit.SHA256.GH
|
||||
float(parsed_stats["GHSmm"][0]), HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
@@ -268,7 +269,7 @@ class AvalonMiner(CGMiner):
|
||||
try:
|
||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
return float(parsed_stats["Temp"])
|
||||
return float(parsed_stats["Temp"][0])
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -283,7 +284,7 @@ class AvalonMiner(CGMiner):
|
||||
try:
|
||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
return int(parsed_stats["MPO"])
|
||||
return int(parsed_stats["MPO"][0])
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -304,7 +305,7 @@ class AvalonMiner(CGMiner):
|
||||
|
||||
for fan in range(self.expected_fans):
|
||||
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):
|
||||
pass
|
||||
return fans_data
|
||||
@@ -322,7 +323,7 @@ class AvalonMiner(CGMiner):
|
||||
try:
|
||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||
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
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -228,4 +228,4 @@ class BFGMiner(StockFirmware):
|
||||
expected_rate, HashUnit.SHA256.from_str(rate_unit)
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
pass
|
||||
@@ -25,6 +25,10 @@ BITAXE_DATA_LOC = DataLocations(
|
||||
"_get_hashboards",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
|
||||
@@ -20,13 +20,18 @@ from pathlib import Path
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import aiofiles
|
||||
import toml
|
||||
import tomli_w
|
||||
|
||||
try:
|
||||
import tomllib
|
||||
except ImportError:
|
||||
import tomli as tomllib
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.config.mining import MiningModePowerTune
|
||||
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
|
||||
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
||||
from pyasic.data.pools import PoolMetrics
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
@@ -177,10 +182,10 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
raw_data = await self.ssh.get_config_file()
|
||||
|
||||
try:
|
||||
toml_data = toml.loads(raw_data)
|
||||
toml_data = tomllib.loads(raw_data)
|
||||
cfg = MinerConfig.from_bosminer(toml_data)
|
||||
self.config = cfg
|
||||
except toml.TomlDecodeError as e:
|
||||
except tomllib.TOMLDecodeError as e:
|
||||
raise APIError("Failed to decode toml when getting config.") from e
|
||||
except TypeError as e:
|
||||
raise APIError("Failed to decode toml when getting config.") from e
|
||||
@@ -189,10 +194,9 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
self.config = config
|
||||
print(config)
|
||||
parsed_cfg = config.as_bosminer(user_suffix=user_suffix)
|
||||
|
||||
toml_conf = toml.dumps(
|
||||
toml_conf = tomli_w.dumps(
|
||||
{
|
||||
"format": {
|
||||
"version": "2.0",
|
||||
@@ -592,6 +596,8 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
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"),
|
||||
@@ -599,7 +605,7 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
remote_failures=pool_info.get("Remote Failures"),
|
||||
active=pool_info.get("Stratum Active"),
|
||||
alive=pool_info.get("Status") == "Alive",
|
||||
url=pool_info.get("URL"),
|
||||
url=pool_url,
|
||||
user=pool_info.get("User"),
|
||||
index=pool_info.get("POOL"),
|
||||
)
|
||||
@@ -720,6 +726,9 @@ BOSER_DATA_LOC = DataLocations(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools", [WebAPICommand("grpc_pool_groups", "get_pool_groups")]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -781,6 +790,11 @@ class BOSer(BraiinsOSFirmware):
|
||||
|
||||
return MinerConfig.from_boser(grpc_conf)
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
boser_cfg = config.as_boser(user_suffix=user_suffix)
|
||||
for key in boser_cfg:
|
||||
await self.web.send_command(key, message=boser_cfg[key])
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
try:
|
||||
result = await self.web.set_power_target(
|
||||
@@ -912,8 +926,10 @@ class BOSer(BraiinsOSFirmware):
|
||||
pass
|
||||
|
||||
if grpc_hashboards is not None:
|
||||
for board in grpc_hashboards["hashboards"]:
|
||||
idx = int(board["id"]) - 1
|
||||
grpc_boards = sorted(
|
||||
grpc_hashboards["hashboards"], key=lambda x: int(x["id"])
|
||||
)
|
||||
for idx, board in enumerate(grpc_boards):
|
||||
if board.get("chipsCount") is not None:
|
||||
hashboards[idx].chips = board["chipsCount"]
|
||||
if board.get("boardTemp") is not None:
|
||||
@@ -937,7 +953,7 @@ class BOSer(BraiinsOSFirmware):
|
||||
async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
|
||||
if grpc_miner_stats is None:
|
||||
try:
|
||||
grpc_miner_stats = self.web.get_miner_stats()
|
||||
grpc_miner_stats = await self.web.get_miner_stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -969,7 +985,7 @@ class BOSer(BraiinsOSFirmware):
|
||||
async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]:
|
||||
if grpc_cooling_state is None:
|
||||
try:
|
||||
grpc_cooling_state = self.web.get_cooling_state()
|
||||
grpc_cooling_state = await self.web.get_cooling_state()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -1058,3 +1074,27 @@ class BOSer(BraiinsOSFirmware):
|
||||
return int(rpc_summary["SUMMARY"][0]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_pools(self, grpc_pool_groups: dict = None) -> List[PoolMetrics]:
|
||||
if grpc_pool_groups is None:
|
||||
try:
|
||||
grpc_pool_groups = await self.web.get_pool_groups()
|
||||
except APIError:
|
||||
return []
|
||||
pools_data = []
|
||||
for group in grpc_pool_groups["poolGroups"]:
|
||||
for idx, pool_info in enumerate(group["pools"]):
|
||||
pool_data = PoolMetrics(
|
||||
url=pool_info["url"],
|
||||
user=pool_info["user"],
|
||||
index=idx,
|
||||
accepted=pool_info["stats"].get("acceptedShares", 0),
|
||||
rejected=pool_info["stats"].get("rejectedShares", 0),
|
||||
get_failures=0,
|
||||
remote_failures=0,
|
||||
active=pool_info.get("active", False),
|
||||
alive=pool_info["alive"],
|
||||
)
|
||||
pools_data.append(pool_data)
|
||||
|
||||
return pools_data
|
||||
|
||||
@@ -27,6 +27,7 @@ from pyasic.errors import APIError
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.miners.device.firmware import StockFirmware
|
||||
from pyasic.rpc.btminer import BTMinerRPCAPI
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
|
||||
BTMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
@@ -109,6 +110,10 @@ BTMINER_DATA_LOC = DataLocations(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools",
|
||||
[RPCAPICommand("rpc_pools", "pools")],
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -655,13 +660,42 @@ class BTMiner(StockFirmware):
|
||||
except LookupError:
|
||||
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.
|
||||
|
||||
Args:
|
||||
file (Path): The local file path of the firmware to be uploaded.
|
||||
token (str): The authentication token for the firmware upgrade.
|
||||
|
||||
Returns:
|
||||
str: Confirmation message after upgrading the firmware.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import AlgoHashRate, HashUnit
|
||||
@@ -22,6 +22,7 @@ from pyasic.errors import APIError
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.miners.device.firmware import StockFirmware
|
||||
from pyasic.rpc.cgminer import CGMinerRPCAPI
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
|
||||
CGMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
@@ -53,6 +54,10 @@ CGMINER_DATA_LOC = DataLocations(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools",
|
||||
[RPCAPICommand("rpc_pools", "pools")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -136,3 +141,33 @@ class CGMiner(StockFirmware):
|
||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
|
||||
if rpc_pools is None:
|
||||
try:
|
||||
rpc_pools = await self.rpc.pools()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
pools_data = []
|
||||
if rpc_pools is not None:
|
||||
try:
|
||||
pools = rpc_pools.get("POOLS", [])
|
||||
for pool_info in pools:
|
||||
url = pool_info.get("URL")
|
||||
pool_url = PoolUrl.from_str(url) if url else None
|
||||
pool_data = PoolMetrics(
|
||||
accepted=pool_info.get("Accepted"),
|
||||
rejected=pool_info.get("Rejected"),
|
||||
get_failures=pool_info.get("Get Failures"),
|
||||
remote_failures=pool_info.get("Remote Failures"),
|
||||
active=pool_info.get("Stratum Active"),
|
||||
alive=pool_info.get("Status") == "Alive",
|
||||
url=pool_url,
|
||||
user=pool_info.get("User"),
|
||||
index=pool_info.get("POOL"),
|
||||
)
|
||||
pools_data.append(pool_data)
|
||||
except LookupError:
|
||||
pass
|
||||
return pools_data
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
@@ -452,3 +453,17 @@ class ePIC(ePICFirmware):
|
||||
return pool_data
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def upgrade_firmware(self, file: Path | str, keep_settings: bool = True) -> bool:
|
||||
|
||||
"""
|
||||
Upgrade the firmware of the ePIC miner device.
|
||||
|
||||
Args:
|
||||
file (Path | str): The local file path of the firmware to be uploaded.
|
||||
keep_settings (bool): Whether to keep the current settings after the update.
|
||||
|
||||
Returns:
|
||||
bool: Whether the firmware update succeeded.
|
||||
"""
|
||||
return await self.web.system_update(file=file, keep_settings=keep_settings)
|
||||
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
|
||||
@@ -29,6 +29,7 @@ from pyasic.miners.data import (
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.web.innosilicon import InnosiliconWebAPI
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
|
||||
INNOSILICON_DATA_LOC = DataLocations(
|
||||
**{
|
||||
@@ -90,6 +91,10 @@ INNOSILICON_DATA_LOC = DataLocations(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools",
|
||||
[RPCAPICommand("rpc_pools", "pools")]
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -111,7 +116,7 @@ class Innosilicon(CGMiner):
|
||||
except APIError:
|
||||
return self.config
|
||||
|
||||
self.config = MinerConfig.from_inno(pools)
|
||||
self.config = MinerConfig.from_inno([pools])
|
||||
return self.config
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
@@ -365,3 +370,33 @@ class Innosilicon(CGMiner):
|
||||
level = int(level)
|
||||
limit = 1250 + (250 * level)
|
||||
return limit
|
||||
|
||||
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
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from typing import List, Optional
|
||||
import logging
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.miners.device.firmware import LuxOSFirmware
|
||||
@@ -51,6 +53,9 @@ LUXMINER_DATA_LOC = DataLocations(
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime", [RPCAPICommand("rpc_stats", "stats")]
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools", [RPCAPICommand("rpc_pools", "pools")]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -142,6 +147,22 @@ class LUXMiner(LuxOSFirmware):
|
||||
async def get_config(self) -> MinerConfig:
|
||||
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}) ###
|
||||
##################################################
|
||||
@@ -297,3 +318,33 @@ class LUXMiner(LuxOSFirmware):
|
||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
|
||||
if rpc_pools is None:
|
||||
try:
|
||||
rpc_pools = await self.rpc.pools()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
pools_data = []
|
||||
if rpc_pools is not None:
|
||||
try:
|
||||
pools = rpc_pools.get("POOLS", [])
|
||||
for pool_info in pools:
|
||||
url = pool_info.get("URL")
|
||||
pool_url = PoolUrl.from_str(url) if url else None
|
||||
pool_data = PoolMetrics(
|
||||
accepted=pool_info.get("Accepted"),
|
||||
rejected=pool_info.get("Rejected"),
|
||||
get_failures=pool_info.get("Get Failures"),
|
||||
remote_failures=pool_info.get("Remote Failures"),
|
||||
active=pool_info.get("Stratum Active"),
|
||||
alive=pool_info.get("Status") == "Alive",
|
||||
url=pool_url,
|
||||
user=pool_info.get("User"),
|
||||
index=pool_info.get("POOL"),
|
||||
)
|
||||
pools_data.append(pool_data)
|
||||
except LookupError:
|
||||
pass
|
||||
return pools_data
|
||||
|
||||
@@ -7,7 +7,9 @@ from pyasic.errors import APIError
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||
from pyasic.miners.device.firmware import MaraFirmware
|
||||
from pyasic.misc import merge_dicts
|
||||
from pyasic.rpc.marathon import MaraRPCAPI
|
||||
from pyasic.web.marathon import MaraWebAPI
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
|
||||
MARA_DATA_LOC = DataLocations(
|
||||
**{
|
||||
@@ -59,11 +61,17 @@ MARA_DATA_LOC = DataLocations(
|
||||
"_get_uptime",
|
||||
[WebAPICommand("web_brief", "brief")],
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools",
|
||||
[WebAPICommand("web_pools", "pools")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class MaraMiner(MaraFirmware):
|
||||
_rpc_cls = MaraRPCAPI
|
||||
rpc: MaraRPCAPI
|
||||
_web_cls = MaraWebAPI
|
||||
web: MaraWebAPI
|
||||
|
||||
@@ -302,3 +310,40 @@ class MaraMiner(MaraFirmware):
|
||||
return web_miner_config["mode"]["concorde"]["power-target"]
|
||||
except LookupError:
|
||||
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
|
||||
|
||||
@@ -560,5 +560,18 @@ class BaseMiner(MinerProtocol):
|
||||
if self._ssh_cls is not None:
|
||||
self.ssh = self._ssh_cls(ip)
|
||||
|
||||
async def upgrade_firmware(self, *, file: str = None, url: str = None, version: str = None, keep_settings: bool = True) -> bool:
|
||||
"""Upgrade the firmware of the miner.
|
||||
|
||||
Parameters:
|
||||
file (str, optional): The file path to the firmware to upgrade from. Must be a valid file path if provided.
|
||||
url (str, optional): The URL to download the firmware from. Must be a valid URL if provided.
|
||||
version (str, optional): The version of the firmware to upgrade to. If None, the version will be inferred from the file or URL.
|
||||
keep_settings (bool, optional): Whether to keep the current settings during the upgrade. Defaults to True.
|
||||
|
||||
Returns:
|
||||
A boolean value of the success of the firmware upgrade.
|
||||
"""
|
||||
return False
|
||||
|
||||
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
|
||||
|
||||
@@ -43,4 +43,4 @@ class LuxOSFirmware(BaseMiner):
|
||||
|
||||
|
||||
class MaraFirmware(BaseMiner):
|
||||
firmware = MinerFirmware.MARATHON
|
||||
firmware = MinerFirmware.MARATHON
|
||||
@@ -48,3 +48,7 @@ class ePICMake(BaseMiner):
|
||||
|
||||
class BitAxeMake(BaseMiner):
|
||||
make = MinerMake.BITAXE
|
||||
|
||||
|
||||
class IceRiverMake(BaseMiner):
|
||||
make = MinerMake.BITAXE
|
||||
|
||||
@@ -19,5 +19,6 @@ from .auradine import *
|
||||
from .avalonminer import *
|
||||
from .epic import *
|
||||
from .goldshell import *
|
||||
from .iceriver import *
|
||||
from .innosilicon import *
|
||||
from .whatsminer import *
|
||||
|
||||
@@ -22,3 +22,10 @@ class S21(AntMinerMake):
|
||||
|
||||
expected_chips = 108
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S21Pro(AntMinerMake):
|
||||
raw_model = MinerModel.ANTMINER.S21Pro
|
||||
|
||||
expected_chips = 65
|
||||
expected_fans = 4
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .S21 import S21
|
||||
from .S21 import S21, S21Pro
|
||||
from .T21 import T21
|
||||
|
||||
@@ -20,3 +20,4 @@ from .A9X import *
|
||||
from .A10X import *
|
||||
from .A11X 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 *
|
||||
@@ -69,6 +69,8 @@ class M50VH60(WhatsMinerMake):
|
||||
class M50VH70(WhatsMinerMake):
|
||||
raw_model = MinerModel.WHATSMINER.M50VH70
|
||||
|
||||
expected_chips = 105
|
||||
|
||||
|
||||
class M50VH80(WhatsMinerMake):
|
||||
raw_model = MinerModel.WHATSMINER.M50VH80
|
||||
@@ -83,6 +85,10 @@ class M50VJ10(WhatsMinerMake):
|
||||
class M50VJ20(WhatsMinerMake):
|
||||
raw_model = MinerModel.WHATSMINER.M50VJ20
|
||||
|
||||
expected_chips = 111
|
||||
|
||||
|
||||
class M50VJ30(WhatsMinerMake):
|
||||
raw_model = MinerModel.WHATSMINER.M50VJ30
|
||||
|
||||
expected_chips = 117
|
||||
|
||||
@@ -51,3 +51,5 @@ class M50SVH40(WhatsMinerMake):
|
||||
|
||||
class M50SVH50(WhatsMinerMake):
|
||||
raw_model = MinerModel.WHATSMINER.M50SVH50
|
||||
|
||||
expected_chips = 135
|
||||
|
||||
@@ -38,6 +38,7 @@ from pyasic.miners.bitaxe import *
|
||||
from pyasic.miners.blockminer import *
|
||||
from pyasic.miners.device.makes import *
|
||||
from pyasic.miners.goldshell import *
|
||||
from pyasic.miners.iceriver import *
|
||||
from pyasic.miners.innosilicon import *
|
||||
from pyasic.miners.whatsminer import *
|
||||
|
||||
@@ -56,6 +57,7 @@ class MinerTypes(enum.Enum):
|
||||
AURADINE = 10
|
||||
MARATHON = 11
|
||||
BITAXE = 12
|
||||
ICERIVER = 13
|
||||
|
||||
|
||||
MINER_CLASSES = {
|
||||
@@ -97,6 +99,7 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19K PRO": BMMinerS19KPro,
|
||||
"ANTMINER T19": BMMinerT19,
|
||||
"ANTMINER S21": BMMinerS21,
|
||||
"ANTMINER S21 PRO": BMMinerS21Pro,
|
||||
"ANTMINER T21": BMMinerT21,
|
||||
},
|
||||
MinerTypes.WHATSMINER: {
|
||||
@@ -329,6 +332,7 @@ MINER_CLASSES = {
|
||||
"AVALONMINER 1066": CGMinerAvalon1066,
|
||||
"AVALONMINER 1166PRO": CGMinerAvalon1166Pro,
|
||||
"AVALONMINER 1246": CGMinerAvalon1246,
|
||||
"AVALONMINER NANO3": CGMinerAvalonNano3,
|
||||
},
|
||||
MinerTypes.INNOSILICON: {
|
||||
None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}),
|
||||
@@ -369,8 +373,10 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19K PRO NOPIC": BOSMinerS19kProNoPIC,
|
||||
"ANTMINER S19K PRO": BOSMinerS19kProNoPIC,
|
||||
"ANTMINER S19 XP": BOSMinerS19XP,
|
||||
"ANTMINER S19 PRO+ HYD.": BOSMinerS19ProPlusHydro,
|
||||
"ANTMINER T19": BOSMinerT19,
|
||||
"ANTMINER S21": BOSMinerS21,
|
||||
"ANTMINER T21": BOSMinerT21,
|
||||
},
|
||||
MinerTypes.VNISH: {
|
||||
None: VNish,
|
||||
@@ -386,6 +392,7 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19J PRO": VNishS19jPro,
|
||||
"ANTMINER S19A": VNishS19a,
|
||||
"ANTMINER S19A PRO": VNishS19aPro,
|
||||
"ANTMINER S19 PRO HYD.": VNishS19ProHydro,
|
||||
"ANTMINER T19": VNishT19,
|
||||
"ANTMINER S21": VNishS21,
|
||||
},
|
||||
@@ -399,6 +406,7 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19K PRO": ePICS19kPro,
|
||||
"ANTMINER S19 XP": ePICS19XP,
|
||||
"ANTMINER S21": ePICS21,
|
||||
"ANTMINER S21 PRO": ePICS21Pro,
|
||||
"ANTMINER T21": ePICT21,
|
||||
"BLOCKMINER 520I": ePICBlockMiner520i,
|
||||
"BLOCKMINER 720I": ePICBlockMiner720i,
|
||||
@@ -447,6 +455,10 @@ MINER_CLASSES = {
|
||||
"BM1366": BitAxeUltra,
|
||||
"BM1397": BitAxeMax,
|
||||
},
|
||||
MinerTypes.ICERIVER: {
|
||||
None: type("IceRiverUnknown", (IceRiver, IceRiverMake), {}),
|
||||
"KS2": IceRiverKS2,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -619,6 +631,8 @@ class MinerFactory:
|
||||
return MinerTypes.INNOSILICON
|
||||
if "Miner UI" in web_text:
|
||||
return MinerTypes.AURADINE
|
||||
if "<TITLE>用户界面</TITLE>" in web_text:
|
||||
return MinerTypes.ICERIVER
|
||||
|
||||
async def _get_miner_socket(self, ip: str) -> MinerTypes | None:
|
||||
commands = ["version", "devdetails"]
|
||||
@@ -685,8 +699,6 @@ class MinerFactory:
|
||||
return MinerTypes.BRAIINS_OS
|
||||
if "BTMINER" in upper_data or "BITMICRO" in upper_data:
|
||||
return MinerTypes.WHATSMINER
|
||||
if "VNISH" in upper_data or "DEVICE PATH" in upper_data:
|
||||
return MinerTypes.VNISH
|
||||
if "HIVEON" in upper_data:
|
||||
return MinerTypes.HIVEON
|
||||
if "LUXMINER" in upper_data:
|
||||
@@ -705,6 +717,8 @@ class MinerFactory:
|
||||
return MinerTypes.AVALONMINER
|
||||
if "GCMINER" in upper_data or "FLUXOS" in upper_data:
|
||||
return MinerTypes.AURADINE
|
||||
if "VNISH" in upper_data or "DEVICE PATH" in upper_data:
|
||||
return MinerTypes.VNISH
|
||||
|
||||
async def send_web_command(
|
||||
self,
|
||||
@@ -794,7 +808,9 @@ class MinerFactory:
|
||||
str_data = str_data.replace("info", "1nfo")
|
||||
str_data = str_data.replace("inf", "0")
|
||||
str_data = str_data.replace("1nfo", "info")
|
||||
str_data = str_data.replace("nano", "n4no")
|
||||
str_data = str_data.replace("nan", "0")
|
||||
str_data = str_data.replace("n4no", "nano")
|
||||
# fix whatever this garbage from avalonminers is `,"id":1}`
|
||||
if str_data.startswith(","):
|
||||
str_data = f"{{{str_data[1:]}"
|
||||
@@ -832,7 +848,9 @@ class MinerFactory:
|
||||
|
||||
async def _get_model_antminer_web(self, ip: str) -> str | None:
|
||||
# last resort, this is slow
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
auth = httpx.DigestAuth(
|
||||
"root", settings.get("default_antminer_web_password", "root")
|
||||
)
|
||||
web_json_data = await self.send_web_command(
|
||||
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
||||
)
|
||||
@@ -892,10 +910,12 @@ class MinerFactory:
|
||||
async def get_miner_model_avalonminer(self, ip: str) -> str | None:
|
||||
sock_json_data = await self.send_api_command(ip, "version")
|
||||
try:
|
||||
miner_model = sock_json_data["VERSION"][0]["PROD"]
|
||||
miner_model = sock_json_data["VERSION"][0]["PROD"].upper()
|
||||
if "-" in miner_model:
|
||||
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
|
||||
except (TypeError, LookupError):
|
||||
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 *
|
||||
@@ -144,12 +144,9 @@ class MinerNetwork:
|
||||
Returns:
|
||||
An asynchronous generator containing found miners.
|
||||
"""
|
||||
# get the current event loop
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
# create a list of scan tasks
|
||||
miners = asyncio.as_completed(
|
||||
[loop.create_task(self.ping_and_get_miner(host)) for host in self.hosts]
|
||||
[asyncio.create_task(self.ping_and_get_miner(host)) for host in self.hosts]
|
||||
)
|
||||
for miner in miners:
|
||||
try:
|
||||
|
||||
@@ -215,14 +215,18 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
return b"{}"
|
||||
|
||||
# send the command
|
||||
data_task = asyncio.create_task(self._read_bytes(reader, timeout=timeout))
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Writing")
|
||||
writer.write(data)
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
||||
await writer.drain()
|
||||
try:
|
||||
data_task = asyncio.create_task(self._read_bytes(reader, timeout=timeout))
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Writing")
|
||||
writer.write(data)
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
||||
await writer.drain()
|
||||
|
||||
await data_task
|
||||
ret_data = data_task.result()
|
||||
await data_task
|
||||
ret_data = data_task.result()
|
||||
except TimeoutError:
|
||||
logging.warning(f"{self} - ([Hidden] Send Bytes) - Read timeout expired.")
|
||||
return b"{}"
|
||||
|
||||
# close the connection
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Closing")
|
||||
@@ -267,7 +271,10 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
str_data = str_data.replace("info", "1nfo")
|
||||
str_data = str_data.replace("inf", "0")
|
||||
str_data = str_data.replace("1nfo", "info")
|
||||
str_data = str_data.replace("nano", "n4no")
|
||||
str_data = str_data.replace("nan", "0")
|
||||
str_data = str_data.replace("n4no", "nano")
|
||||
|
||||
# fix whatever this garbage from avalonminers is `,"id":1}`
|
||||
if str_data.startswith(","):
|
||||
str_data = f"{{{str_data[1:]}"
|
||||
|
||||
@@ -749,3 +749,12 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
||||
</details>
|
||||
"""
|
||||
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")
|
||||
33
pyasic/rpc/marathon.py
Normal file
33
pyasic/rpc/marathon.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from pyasic.rpc.base import BaseMinerRPCAPI
|
||||
|
||||
|
||||
class MaraRPCAPI(BaseMinerRPCAPI):
|
||||
"""An abstraction of the MaraFW API.
|
||||
|
||||
Each method corresponds to an API command in MaraFW.
|
||||
|
||||
No documentation for this API is currently publicly available.
|
||||
|
||||
Additionally, every command not included here just returns the result of the `summary` command.
|
||||
|
||||
This class abstracts use of the MaraFW API, as well as the
|
||||
methods for sending commands to it. The `self.send_command()`
|
||||
function handles sending a command to the miner asynchronously, and
|
||||
as such is the base for many of the functions in this class, which
|
||||
rely on it to send the command for them.
|
||||
"""
|
||||
|
||||
async def summary(self):
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def devs(self):
|
||||
return await self.send_command("devs")
|
||||
|
||||
async def pools(self):
|
||||
return await self.send_command("pools")
|
||||
|
||||
async def stats(self):
|
||||
return await self.send_command("stats")
|
||||
|
||||
async def version(self):
|
||||
return await self.send_command("version")
|
||||
@@ -13,8 +13,6 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
import socket
|
||||
import struct
|
||||
from ssl import SSLContext
|
||||
from typing import Any, Union
|
||||
|
||||
@@ -39,25 +37,19 @@ _settings = { # defaults
|
||||
"default_auradine_web_password": "admin",
|
||||
"default_epic_web_password": "letmein",
|
||||
"default_hive_web_password": "admin",
|
||||
"default_iceriver_web_password": "12345678",
|
||||
"default_antminer_ssh_password": "miner",
|
||||
"default_bosminer_ssh_password": "root",
|
||||
"socket_linger_time": 1000,
|
||||
}
|
||||
|
||||
|
||||
ssl_cxt = httpx.create_ssl_context()
|
||||
|
||||
|
||||
# this function configures socket options like SO_LINGER and returns an AsyncHTTPTransport instance to perform asynchronous HTTP requests
|
||||
# this function returns an AsyncHTTPTransport instance to perform asynchronous HTTP requests
|
||||
# using those options.
|
||||
# SO_LINGER controls what happens when you close a socket with unsent data - it allows specifying linger time for the data to be sent.
|
||||
def transport(verify: Union[str, bool, SSLContext] = ssl_cxt):
|
||||
l_onoff = 1
|
||||
l_linger = get("so_linger_time", 1000)
|
||||
|
||||
opts = [(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", l_onoff, l_linger))]
|
||||
|
||||
return AsyncHTTPTransport(socket_options=opts, verify=verify)
|
||||
return AsyncHTTPTransport(verify=verify)
|
||||
|
||||
|
||||
def get(key: str, other: Any = None) -> Any:
|
||||
|
||||
@@ -19,5 +19,6 @@ from .base import BaseWebAPI
|
||||
from .braiins_os import BOSerWebAPI, BOSMinerWebAPI
|
||||
from .epic import ePICWebAPI
|
||||
from .goldshell import GoldshellWebAPI
|
||||
from .iceriver import IceRiverWebAPI
|
||||
from .innosilicon import InnosiliconWebAPI
|
||||
from .vnish import VNishWebAPI
|
||||
|
||||
@@ -18,8 +18,9 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import aiofiles
|
||||
import httpx
|
||||
from pathlib import Path
|
||||
|
||||
from pyasic import settings
|
||||
from pyasic.web.base import BaseWebAPI
|
||||
@@ -59,9 +60,8 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
transport=settings.transport(),
|
||||
) as client:
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
|
||||
if parameters:
|
||||
data = await client.post(
|
||||
url,
|
||||
@@ -71,14 +71,15 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
)
|
||||
else:
|
||||
data = await client.get(url, auth=auth)
|
||||
except httpx.HTTPError:
|
||||
pass
|
||||
except httpx.HTTPError as e:
|
||||
return {"success": False, "message": f"HTTP error occurred: {str(e)}"}
|
||||
else:
|
||||
if data.status_code == 200:
|
||||
try:
|
||||
return data.json()
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
return {"success": False, "message": "Failed to decode JSON"}
|
||||
return {"success": False, "message": "Unknown error occurred"}
|
||||
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||
@@ -403,3 +404,20 @@ class AntminerOldWebAPI(BaseWebAPI):
|
||||
dict: Information about the mining pools configured in the miner.
|
||||
"""
|
||||
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
|
||||
)
|
||||
|
||||
@@ -414,15 +414,6 @@ class BOSerWebAPI(BaseWebAPI):
|
||||
"get_pool_groups", message=GetPoolGroupsRequest(), privileged=True
|
||||
)
|
||||
|
||||
async def create_pool_group(self) -> dict:
|
||||
raise NotImplementedError
|
||||
|
||||
async def update_pool_group(self) -> dict:
|
||||
raise NotImplementedError
|
||||
|
||||
async def remove_pool_group(self) -> dict:
|
||||
raise NotImplementedError
|
||||
|
||||
async def get_miner_configuration(self) -> dict:
|
||||
return await self.send_command(
|
||||
"get_miner_configuration",
|
||||
@@ -490,3 +481,15 @@ class BOSerWebAPI(BaseWebAPI):
|
||||
),
|
||||
privileged=True,
|
||||
)
|
||||
|
||||
async def set_pool_groups(
|
||||
self,
|
||||
pool_groups: List[PoolGroupConfiguration],
|
||||
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
|
||||
) -> dict:
|
||||
return await self.send_command(
|
||||
"set_pool_groups",
|
||||
message=SetPoolGroupsRequest(
|
||||
save_action=save_action, pool_groups=pool_groups
|
||||
),
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class ApiVersion(betterproto.Message):
|
||||
"""LATEST_API_VERSION=1.0.0-beta.4"""
|
||||
"""LATEST_API_VERSION=1.2.0"""
|
||||
|
||||
major: int = betterproto.uint64_field(1)
|
||||
minor: int = betterproto.uint64_field(2)
|
||||
@@ -52,6 +52,7 @@ class ApiVersionServiceStub(betterproto.ServiceStub):
|
||||
|
||||
|
||||
class ApiVersionServiceBase(ServiceBase):
|
||||
|
||||
async def get_api_version(
|
||||
self, api_version_request: "ApiVersionRequest"
|
||||
) -> "ApiVersion":
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# sources: bos/v1/actions.proto, bos/v1/authentication.proto, bos/v1/common.proto, bos/v1/configuration.proto, bos/v1/constraints.proto, bos/v1/cooling.proto, bos/v1/license.proto, bos/v1/miner.proto, bos/v1/performance.proto, bos/v1/pool.proto, bos/v1/units.proto, bos/v1/work.proto
|
||||
# sources: bos/v1/actions.proto, bos/v1/authentication.proto, bos/v1/common.proto, bos/v1/configuration.proto, bos/v1/constraints.proto, bos/v1/cooling.proto, bos/v1/license.proto, bos/v1/miner.proto, bos/v1/network.proto, bos/v1/performance.proto, bos/v1/pool.proto, bos/v1/units.proto, bos/v1/work.proto
|
||||
# plugin: python-betterproto
|
||||
# This file has been @generated
|
||||
import warnings
|
||||
@@ -69,6 +69,7 @@ class Platform(betterproto.Enum):
|
||||
PLATFORM_AM3_AML = 4
|
||||
PLATFORM_STM32MP157C_II1_AM2 = 5
|
||||
PLATFORM_CVITEK_BM1_AM2 = 6
|
||||
PLATFORM_ZYNQ_BM3_AM2 = 7
|
||||
|
||||
|
||||
class BosMode(betterproto.Enum):
|
||||
@@ -134,6 +135,12 @@ class SupportArchiveFormat(betterproto.Enum):
|
||||
"""BOS custom format"""
|
||||
|
||||
|
||||
class NetworkProtocol(betterproto.Enum):
|
||||
NETWORK_PROTOCOL_UNSPECIFIED = 0
|
||||
NETWORK_PROTOCOL_DHCP = 1
|
||||
NETWORK_PROTOCOL_STATIC = 2
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class StartRequest(betterproto.Message):
|
||||
"""Request for start bosminer action."""
|
||||
@@ -256,7 +263,14 @@ class LoginRequest(betterproto.Message):
|
||||
class LoginResponse(betterproto.Message):
|
||||
"""Response for login action."""
|
||||
|
||||
pass
|
||||
token: str = betterproto.string_field(1)
|
||||
"""Token to be used for authentication"""
|
||||
|
||||
timeout_s: int = betterproto.uint32_field(2)
|
||||
"""
|
||||
Authentication token validity/timeout in seconds. Token validity refreshed
|
||||
to this value with each request.
|
||||
"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
@@ -555,7 +569,16 @@ class TunerConfiguration(betterproto.Message):
|
||||
@dataclass(eq=False, repr=False)
|
||||
class TunerConstraints(betterproto.Message):
|
||||
power_target: "PowerConstraints" = betterproto.message_field(1)
|
||||
"""Tuner power target mode constraints"""
|
||||
|
||||
hashrate_target: "HashrateConstraints" = betterproto.message_field(2)
|
||||
"""Tuner hashrate target mode constraints"""
|
||||
|
||||
enabled: "BooleanConstraint" = betterproto.message_field(3)
|
||||
"""Tuner enabled enabled default value"""
|
||||
|
||||
default_mode: "TunerMode" = betterproto.enum_field(4)
|
||||
"""Default tuner mode"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
@@ -618,6 +641,9 @@ class DpsConstraints(betterproto.Message):
|
||||
shutdown_duration: "DurationConstraints" = betterproto.message_field(6)
|
||||
"""Dynamic Performance Scaling shutdown duration constraints"""
|
||||
|
||||
enabled: "BooleanConstraint" = betterproto.message_field(7)
|
||||
"""Dynamic Performance Scaling enabled default value"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class HashboardConstraints(betterproto.Message):
|
||||
@@ -1104,6 +1130,11 @@ class PoolStats(betterproto.Message):
|
||||
generated_work: int = betterproto.uint64_field(6)
|
||||
"""Generated work"""
|
||||
|
||||
last_share_time: Optional[datetime] = betterproto.message_field(
|
||||
7, optional=True, group="_last_share_time"
|
||||
)
|
||||
"""Last share time"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class GetPoolGroupsRequest(betterproto.Message):
|
||||
@@ -1184,6 +1215,33 @@ class RemovePoolGroupResponse(betterproto.Message):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class SetPoolGroupsRequest(betterproto.Message):
|
||||
"""Request for setting pool groups"""
|
||||
|
||||
save_action: "SaveAction" = betterproto.enum_field(1)
|
||||
"""
|
||||
Save action SAVE just update config but changes will not be applied
|
||||
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.
|
||||
"""
|
||||
|
||||
pool_groups: List["PoolGroupConfiguration"] = betterproto.message_field(2)
|
||||
"""
|
||||
Pool groups configuration `uid` must not be specified (it will be
|
||||
generated)
|
||||
"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class SetPoolGroupsResponse(betterproto.Message):
|
||||
"""Response on setting pool groups"""
|
||||
|
||||
pool_groups: List["PoolGroupConfiguration"] = betterproto.message_field(1)
|
||||
"""Configured pool groups"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class GetMinerConfigurationRequest(betterproto.Message):
|
||||
pass
|
||||
@@ -1234,8 +1292,9 @@ class NoneLicense(betterproto.Message):
|
||||
time_to_restricted: int = betterproto.uint32_field(1)
|
||||
"""
|
||||
BOS Initialization timeout - number of seconds elapsed since bosminer start
|
||||
i.e., number of seconds BOS will start mining in restricted mode burning
|
||||
15% of hashrate
|
||||
i.e., number of seconds BOS will start mining in restricted mode burning 5%
|
||||
of hashrate For more, see Section 3.10 of
|
||||
https://braiins.com/os/plus/license
|
||||
"""
|
||||
|
||||
|
||||
@@ -1350,6 +1409,27 @@ class BosVersion(betterproto.Message):
|
||||
bos_plus: bool = betterproto.bool_field(3)
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class ErrorCode(betterproto.Message):
|
||||
code: str = betterproto.string_field(1)
|
||||
reason: str = betterproto.string_field(2)
|
||||
hint: str = betterproto.string_field(3)
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class Component(betterproto.Message):
|
||||
name: str = betterproto.string_field(1)
|
||||
index: int = betterproto.uint32_field(2)
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class MinerError(betterproto.Message):
|
||||
timestamp: str = betterproto.string_field(1)
|
||||
message: str = betterproto.string_field(2)
|
||||
error_codes: List["ErrorCode"] = betterproto.message_field(3)
|
||||
components: List["Component"] = betterproto.message_field(4)
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class GetMinerStatusRequest(betterproto.Message):
|
||||
pass
|
||||
@@ -1402,6 +1482,12 @@ class GetMinerDetailsResponse(betterproto.Message):
|
||||
system_uptime_s: int = betterproto.uint64_field(11)
|
||||
"""System uptime"""
|
||||
|
||||
status: "MinerStatus" = betterproto.enum_field(12)
|
||||
"""Miner status"""
|
||||
|
||||
kernel_version: str = betterproto.string_field(13)
|
||||
"""Kernel version"""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
super().__post_init__()
|
||||
if self.is_set("system_uptime"):
|
||||
@@ -1437,6 +1523,17 @@ class GetMinerStatsResponse(betterproto.Message):
|
||||
"""Miner power stats"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class GetErrorsRequest(betterproto.Message):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class GetErrorsResponse(betterproto.Message):
|
||||
errors: List["MinerError"] = betterproto.message_field(1)
|
||||
"""Array of errors"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class Hashboard(betterproto.Message):
|
||||
"""Structure to handle HB details"""
|
||||
@@ -1467,6 +1564,9 @@ class Hashboard(betterproto.Message):
|
||||
stats: "WorkSolverStats" = betterproto.message_field(8)
|
||||
"""Hashboard stats"""
|
||||
|
||||
model: Optional[str] = betterproto.string_field(9, optional=True, group="_model")
|
||||
"""Hashboard model"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class GetSupportArchiveRequest(betterproto.Message):
|
||||
@@ -1530,6 +1630,108 @@ class HashboardEnableState(betterproto.Message):
|
||||
"""Flag if hashboard is enabled or not"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class GetNetworkConfigurationRequest(betterproto.Message):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class GetNetworkConfigurationResponse(betterproto.Message):
|
||||
network: "NetworkConfiguration" = betterproto.message_field(1)
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class SetNetworkConfigurationRequest(betterproto.Message):
|
||||
dhcp: "Dhcp" = betterproto.message_field(1, group="protocol")
|
||||
static: "Static" = betterproto.message_field(2, group="protocol")
|
||||
hostname: Optional[str] = betterproto.string_field(
|
||||
3, optional=True, group="_hostname"
|
||||
)
|
||||
"""Hostname. Existing value will be preserved if this field is not set."""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class SetNetworkConfigurationResponse(betterproto.Message):
|
||||
network: "NetworkConfiguration" = betterproto.message_field(1)
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class NetworkConfiguration(betterproto.Message):
|
||||
dhcp: "Dhcp" = betterproto.message_field(1, group="protocol")
|
||||
static: "Static" = betterproto.message_field(2, group="protocol")
|
||||
hostname: str = betterproto.string_field(3)
|
||||
"""Hostname"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class Dhcp(betterproto.Message):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class Static(betterproto.Message):
|
||||
address: str = betterproto.string_field(1)
|
||||
"""IP address"""
|
||||
|
||||
netmask: str = betterproto.string_field(2)
|
||||
"""Netmask"""
|
||||
|
||||
gateway: str = betterproto.string_field(3)
|
||||
"""Gateway"""
|
||||
|
||||
dns_servers: List[str] = betterproto.string_field(4)
|
||||
"""DNS servers"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class GetNetworkInfoRequest(betterproto.Message):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class GetNetworkInfoResponse(betterproto.Message):
|
||||
"""
|
||||
Response message for GetCurrentNetworkConfiguration Represents the current
|
||||
network configuration for the default network interface. Only IPv4 is
|
||||
supported.
|
||||
"""
|
||||
|
||||
name: str = betterproto.string_field(1)
|
||||
"""Name of the network interface"""
|
||||
|
||||
mac_address: Optional[str] = betterproto.string_field(
|
||||
2, optional=True, group="_mac_address"
|
||||
)
|
||||
"""MAC address of the network interface"""
|
||||
|
||||
hostname: Optional[str] = betterproto.string_field(
|
||||
3, optional=True, group="_hostname"
|
||||
)
|
||||
"""Miner hostname"""
|
||||
|
||||
protocol: Optional["NetworkProtocol"] = betterproto.enum_field(
|
||||
4, optional=True, group="_protocol"
|
||||
)
|
||||
"""Network protocol"""
|
||||
|
||||
dns_servers: List[str] = betterproto.string_field(5)
|
||||
"""List of configured DNS servers"""
|
||||
|
||||
networks: List["IpNetwork"] = betterproto.message_field(6)
|
||||
"""List of assigned IP addresses"""
|
||||
|
||||
default_gateway: Optional[str] = betterproto.string_field(
|
||||
7, optional=True, group="_default_gateway"
|
||||
)
|
||||
"""Default gateway/route for the interface"""
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=False)
|
||||
class IpNetwork(betterproto.Message):
|
||||
address: str = betterproto.string_field(1)
|
||||
netmask: str = betterproto.string_field(2)
|
||||
|
||||
|
||||
class ActionsServiceStub(betterproto.ServiceStub):
|
||||
async def start(
|
||||
self,
|
||||
@@ -2049,6 +2251,23 @@ class PoolServiceStub(betterproto.ServiceStub):
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
async def set_pool_groups(
|
||||
self,
|
||||
set_pool_groups_request: "SetPoolGroupsRequest",
|
||||
*,
|
||||
timeout: Optional[float] = None,
|
||||
deadline: Optional["Deadline"] = None,
|
||||
metadata: Optional["MetadataLike"] = None
|
||||
) -> "SetPoolGroupsResponse":
|
||||
return await self._unary_unary(
|
||||
"/braiins.bos.v1.PoolService/SetPoolGroups",
|
||||
set_pool_groups_request,
|
||||
SetPoolGroupsResponse,
|
||||
timeout=timeout,
|
||||
deadline=deadline,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
|
||||
class ConfigurationServiceStub(betterproto.ServiceStub):
|
||||
async def get_miner_configuration(
|
||||
@@ -2158,6 +2377,23 @@ class MinerServiceStub(betterproto.ServiceStub):
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
async def get_errors(
|
||||
self,
|
||||
get_errors_request: "GetErrorsRequest",
|
||||
*,
|
||||
timeout: Optional[float] = None,
|
||||
deadline: Optional["Deadline"] = None,
|
||||
metadata: Optional["MetadataLike"] = None
|
||||
) -> "GetErrorsResponse":
|
||||
return await self._unary_unary(
|
||||
"/braiins.bos.v1.MinerService/GetErrors",
|
||||
get_errors_request,
|
||||
GetErrorsResponse,
|
||||
timeout=timeout,
|
||||
deadline=deadline,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
async def get_hashboards(
|
||||
self,
|
||||
get_hashboards_request: "GetHashboardsRequest",
|
||||
@@ -2228,7 +2464,61 @@ class MinerServiceStub(betterproto.ServiceStub):
|
||||
)
|
||||
|
||||
|
||||
class NetworkServiceStub(betterproto.ServiceStub):
|
||||
async def get_network_configuration(
|
||||
self,
|
||||
get_network_configuration_request: "GetNetworkConfigurationRequest",
|
||||
*,
|
||||
timeout: Optional[float] = None,
|
||||
deadline: Optional["Deadline"] = None,
|
||||
metadata: Optional["MetadataLike"] = None
|
||||
) -> "GetNetworkConfigurationResponse":
|
||||
return await self._unary_unary(
|
||||
"/braiins.bos.v1.NetworkService/GetNetworkConfiguration",
|
||||
get_network_configuration_request,
|
||||
GetNetworkConfigurationResponse,
|
||||
timeout=timeout,
|
||||
deadline=deadline,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
async def set_network_configuration(
|
||||
self,
|
||||
set_network_configuration_request: "SetNetworkConfigurationRequest",
|
||||
*,
|
||||
timeout: Optional[float] = None,
|
||||
deadline: Optional["Deadline"] = None,
|
||||
metadata: Optional["MetadataLike"] = None
|
||||
) -> "SetNetworkConfigurationResponse":
|
||||
return await self._unary_unary(
|
||||
"/braiins.bos.v1.NetworkService/SetNetworkConfiguration",
|
||||
set_network_configuration_request,
|
||||
SetNetworkConfigurationResponse,
|
||||
timeout=timeout,
|
||||
deadline=deadline,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
async def get_network_info(
|
||||
self,
|
||||
get_network_info_request: "GetNetworkInfoRequest",
|
||||
*,
|
||||
timeout: Optional[float] = None,
|
||||
deadline: Optional["Deadline"] = None,
|
||||
metadata: Optional["MetadataLike"] = None
|
||||
) -> "GetNetworkInfoResponse":
|
||||
return await self._unary_unary(
|
||||
"/braiins.bos.v1.NetworkService/GetNetworkInfo",
|
||||
get_network_info_request,
|
||||
GetNetworkInfoResponse,
|
||||
timeout=timeout,
|
||||
deadline=deadline,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
|
||||
class ActionsServiceBase(ServiceBase):
|
||||
|
||||
async def start(self, start_request: "StartRequest") -> "StartResponse":
|
||||
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
||||
|
||||
@@ -2357,26 +2647,23 @@ class ActionsServiceBase(ServiceBase):
|
||||
RebootRequest,
|
||||
RebootResponse,
|
||||
),
|
||||
"/braiins.bos.v1.ActionsService/SetLocateDeviceStatus": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_set_locate_device_status,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
SetLocateDeviceStatusRequest,
|
||||
LocateDeviceStatusResponse,
|
||||
)
|
||||
"/braiins.bos.v1.ActionsService/SetLocateDeviceStatus": grpclib.const.Handler(
|
||||
self.__rpc_set_locate_device_status,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
SetLocateDeviceStatusRequest,
|
||||
LocateDeviceStatusResponse,
|
||||
),
|
||||
"/braiins.bos.v1.ActionsService/GetLocateDeviceStatus": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_get_locate_device_status,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
GetLocateDeviceStatusRequest,
|
||||
LocateDeviceStatusResponse,
|
||||
)
|
||||
"/braiins.bos.v1.ActionsService/GetLocateDeviceStatus": grpclib.const.Handler(
|
||||
self.__rpc_get_locate_device_status,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
GetLocateDeviceStatusRequest,
|
||||
LocateDeviceStatusResponse,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class AuthenticationServiceBase(ServiceBase):
|
||||
|
||||
async def login(self, login_request: "LoginRequest") -> "LoginResponse":
|
||||
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
||||
|
||||
@@ -2417,6 +2704,7 @@ class AuthenticationServiceBase(ServiceBase):
|
||||
|
||||
|
||||
class CoolingServiceBase(ServiceBase):
|
||||
|
||||
async def get_cooling_state(
|
||||
self, get_cooling_state_request: "GetCoolingStateRequest"
|
||||
) -> "GetCoolingStateResponse":
|
||||
@@ -2461,6 +2749,7 @@ class CoolingServiceBase(ServiceBase):
|
||||
|
||||
|
||||
class PerformanceServiceBase(ServiceBase):
|
||||
|
||||
async def get_tuner_state(
|
||||
self, get_tuner_state_request: "GetTunerStateRequest"
|
||||
) -> "GetTunerStateResponse":
|
||||
@@ -2648,21 +2937,17 @@ class PerformanceServiceBase(ServiceBase):
|
||||
GetTunerStateRequest,
|
||||
GetTunerStateResponse,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/ListTargetProfiles": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_list_target_profiles,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
ListTargetProfilesRequest,
|
||||
ListTargetProfilesResponse,
|
||||
)
|
||||
"/braiins.bos.v1.PerformanceService/ListTargetProfiles": grpclib.const.Handler(
|
||||
self.__rpc_list_target_profiles,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
ListTargetProfilesRequest,
|
||||
ListTargetProfilesResponse,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/SetDefaultPowerTarget": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_set_default_power_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
SetDefaultPowerTargetRequest,
|
||||
SetPowerTargetResponse,
|
||||
)
|
||||
"/braiins.bos.v1.PerformanceService/SetDefaultPowerTarget": grpclib.const.Handler(
|
||||
self.__rpc_set_default_power_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
SetDefaultPowerTargetRequest,
|
||||
SetPowerTargetResponse,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/SetPowerTarget": grpclib.const.Handler(
|
||||
self.__rpc_set_power_target,
|
||||
@@ -2670,53 +2955,41 @@ class PerformanceServiceBase(ServiceBase):
|
||||
SetPowerTargetRequest,
|
||||
SetPowerTargetResponse,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/IncrementPowerTarget": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_increment_power_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
IncrementPowerTargetRequest,
|
||||
SetPowerTargetResponse,
|
||||
)
|
||||
"/braiins.bos.v1.PerformanceService/IncrementPowerTarget": grpclib.const.Handler(
|
||||
self.__rpc_increment_power_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
IncrementPowerTargetRequest,
|
||||
SetPowerTargetResponse,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/DecrementPowerTarget": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_decrement_power_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
DecrementPowerTargetRequest,
|
||||
SetPowerTargetResponse,
|
||||
)
|
||||
"/braiins.bos.v1.PerformanceService/DecrementPowerTarget": grpclib.const.Handler(
|
||||
self.__rpc_decrement_power_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
DecrementPowerTargetRequest,
|
||||
SetPowerTargetResponse,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/SetDefaultHashrateTarget": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_set_default_hashrate_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
SetDefaultHashrateTargetRequest,
|
||||
SetHashrateTargetResponse,
|
||||
)
|
||||
"/braiins.bos.v1.PerformanceService/SetDefaultHashrateTarget": grpclib.const.Handler(
|
||||
self.__rpc_set_default_hashrate_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
SetDefaultHashrateTargetRequest,
|
||||
SetHashrateTargetResponse,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/SetHashrateTarget": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_set_hashrate_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
SetHashrateTargetRequest,
|
||||
SetHashrateTargetResponse,
|
||||
)
|
||||
"/braiins.bos.v1.PerformanceService/SetHashrateTarget": grpclib.const.Handler(
|
||||
self.__rpc_set_hashrate_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
SetHashrateTargetRequest,
|
||||
SetHashrateTargetResponse,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/IncrementHashrateTarget": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_increment_hashrate_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
IncrementHashrateTargetRequest,
|
||||
SetHashrateTargetResponse,
|
||||
)
|
||||
"/braiins.bos.v1.PerformanceService/IncrementHashrateTarget": grpclib.const.Handler(
|
||||
self.__rpc_increment_hashrate_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
IncrementHashrateTargetRequest,
|
||||
SetHashrateTargetResponse,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/DecrementHashrateTarget": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_decrement_hashrate_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
DecrementHashrateTargetRequest,
|
||||
SetHashrateTargetResponse,
|
||||
)
|
||||
"/braiins.bos.v1.PerformanceService/DecrementHashrateTarget": grpclib.const.Handler(
|
||||
self.__rpc_decrement_hashrate_target,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
DecrementHashrateTargetRequest,
|
||||
SetHashrateTargetResponse,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/SetDPS": grpclib.const.Handler(
|
||||
self.__rpc_set_dps,
|
||||
@@ -2724,34 +2997,29 @@ class PerformanceServiceBase(ServiceBase):
|
||||
SetDpsRequest,
|
||||
SetDpsResponse,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/SetPerformanceMode": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_set_performance_mode,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
SetPerformanceModeRequest,
|
||||
PerformanceMode,
|
||||
)
|
||||
"/braiins.bos.v1.PerformanceService/SetPerformanceMode": grpclib.const.Handler(
|
||||
self.__rpc_set_performance_mode,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
SetPerformanceModeRequest,
|
||||
PerformanceMode,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/GetActivePerformanceMode": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_get_active_performance_mode,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
GetPerformanceModeRequest,
|
||||
PerformanceMode,
|
||||
)
|
||||
"/braiins.bos.v1.PerformanceService/GetActivePerformanceMode": grpclib.const.Handler(
|
||||
self.__rpc_get_active_performance_mode,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
GetPerformanceModeRequest,
|
||||
PerformanceMode,
|
||||
),
|
||||
"/braiins.bos.v1.PerformanceService/RemoveTunedProfiles": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_remove_tuned_profiles,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
RemoveTunedProfilesRequest,
|
||||
RemoveTunedProfilesResponse,
|
||||
)
|
||||
"/braiins.bos.v1.PerformanceService/RemoveTunedProfiles": grpclib.const.Handler(
|
||||
self.__rpc_remove_tuned_profiles,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
RemoveTunedProfilesRequest,
|
||||
RemoveTunedProfilesResponse,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class PoolServiceBase(ServiceBase):
|
||||
|
||||
async def get_pool_groups(
|
||||
self, get_pool_groups_request: "GetPoolGroupsRequest"
|
||||
) -> "GetPoolGroupsResponse":
|
||||
@@ -2772,6 +3040,11 @@ class PoolServiceBase(ServiceBase):
|
||||
) -> "RemovePoolGroupResponse":
|
||||
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
||||
|
||||
async def set_pool_groups(
|
||||
self, set_pool_groups_request: "SetPoolGroupsRequest"
|
||||
) -> "SetPoolGroupsResponse":
|
||||
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
||||
|
||||
async def __rpc_get_pool_groups(
|
||||
self,
|
||||
stream: "grpclib.server.Stream[GetPoolGroupsRequest, GetPoolGroupsResponse]",
|
||||
@@ -2804,6 +3077,14 @@ class PoolServiceBase(ServiceBase):
|
||||
response = await self.remove_pool_group(request)
|
||||
await stream.send_message(response)
|
||||
|
||||
async def __rpc_set_pool_groups(
|
||||
self,
|
||||
stream: "grpclib.server.Stream[SetPoolGroupsRequest, SetPoolGroupsResponse]",
|
||||
) -> None:
|
||||
request = await stream.recv_message()
|
||||
response = await self.set_pool_groups(request)
|
||||
await stream.send_message(response)
|
||||
|
||||
def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
|
||||
return {
|
||||
"/braiins.bos.v1.PoolService/GetPoolGroups": grpclib.const.Handler(
|
||||
@@ -2830,10 +3111,17 @@ class PoolServiceBase(ServiceBase):
|
||||
RemovePoolGroupRequest,
|
||||
RemovePoolGroupResponse,
|
||||
),
|
||||
"/braiins.bos.v1.PoolService/SetPoolGroups": grpclib.const.Handler(
|
||||
self.__rpc_set_pool_groups,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
SetPoolGroupsRequest,
|
||||
SetPoolGroupsResponse,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class ConfigurationServiceBase(ServiceBase):
|
||||
|
||||
async def get_miner_configuration(
|
||||
self, get_miner_configuration_request: "GetMinerConfigurationRequest"
|
||||
) -> "GetMinerConfigurationResponse":
|
||||
@@ -2862,26 +3150,23 @@ class ConfigurationServiceBase(ServiceBase):
|
||||
|
||||
def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
|
||||
return {
|
||||
"/braiins.bos.v1.ConfigurationService/GetMinerConfiguration": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_get_miner_configuration,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
GetMinerConfigurationRequest,
|
||||
GetMinerConfigurationResponse,
|
||||
)
|
||||
"/braiins.bos.v1.ConfigurationService/GetMinerConfiguration": grpclib.const.Handler(
|
||||
self.__rpc_get_miner_configuration,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
GetMinerConfigurationRequest,
|
||||
GetMinerConfigurationResponse,
|
||||
),
|
||||
"/braiins.bos.v1.ConfigurationService/GetConstraints": (
|
||||
grpclib.const.Handler(
|
||||
self.__rpc_get_constraints,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
GetConstraintsRequest,
|
||||
GetConstraintsResponse,
|
||||
)
|
||||
"/braiins.bos.v1.ConfigurationService/GetConstraints": grpclib.const.Handler(
|
||||
self.__rpc_get_constraints,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
GetConstraintsRequest,
|
||||
GetConstraintsResponse,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class LicenseServiceBase(ServiceBase):
|
||||
|
||||
async def get_license_state(
|
||||
self, get_license_state_request: "GetLicenseStateRequest"
|
||||
) -> "GetLicenseStateResponse":
|
||||
@@ -2907,6 +3192,7 @@ class LicenseServiceBase(ServiceBase):
|
||||
|
||||
|
||||
class MinerServiceBase(ServiceBase):
|
||||
|
||||
async def get_miner_status(
|
||||
self, get_miner_status_request: "GetMinerStatusRequest"
|
||||
) -> AsyncIterator["GetMinerStatusResponse"]:
|
||||
@@ -2923,6 +3209,11 @@ class MinerServiceBase(ServiceBase):
|
||||
) -> "GetMinerStatsResponse":
|
||||
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
||||
|
||||
async def get_errors(
|
||||
self, get_errors_request: "GetErrorsRequest"
|
||||
) -> "GetErrorsResponse":
|
||||
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
||||
|
||||
async def get_hashboards(
|
||||
self, get_hashboards_request: "GetHashboardsRequest"
|
||||
) -> "GetHashboardsResponse":
|
||||
@@ -2971,6 +3262,13 @@ class MinerServiceBase(ServiceBase):
|
||||
response = await self.get_miner_stats(request)
|
||||
await stream.send_message(response)
|
||||
|
||||
async def __rpc_get_errors(
|
||||
self, stream: "grpclib.server.Stream[GetErrorsRequest, GetErrorsResponse]"
|
||||
) -> None:
|
||||
request = await stream.recv_message()
|
||||
response = await self.get_errors(request)
|
||||
await stream.send_message(response)
|
||||
|
||||
async def __rpc_get_hashboards(
|
||||
self,
|
||||
stream: "grpclib.server.Stream[GetHashboardsRequest, GetHashboardsResponse]",
|
||||
@@ -3026,6 +3324,12 @@ class MinerServiceBase(ServiceBase):
|
||||
GetMinerStatsRequest,
|
||||
GetMinerStatsResponse,
|
||||
),
|
||||
"/braiins.bos.v1.MinerService/GetErrors": grpclib.const.Handler(
|
||||
self.__rpc_get_errors,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
GetErrorsRequest,
|
||||
GetErrorsResponse,
|
||||
),
|
||||
"/braiins.bos.v1.MinerService/GetHashboards": grpclib.const.Handler(
|
||||
self.__rpc_get_hashboards,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
@@ -3051,3 +3355,67 @@ class MinerServiceBase(ServiceBase):
|
||||
DisableHashboardsResponse,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class NetworkServiceBase(ServiceBase):
|
||||
|
||||
async def get_network_configuration(
|
||||
self, get_network_configuration_request: "GetNetworkConfigurationRequest"
|
||||
) -> "GetNetworkConfigurationResponse":
|
||||
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
||||
|
||||
async def set_network_configuration(
|
||||
self, set_network_configuration_request: "SetNetworkConfigurationRequest"
|
||||
) -> "SetNetworkConfigurationResponse":
|
||||
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
||||
|
||||
async def get_network_info(
|
||||
self, get_network_info_request: "GetNetworkInfoRequest"
|
||||
) -> "GetNetworkInfoResponse":
|
||||
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
||||
|
||||
async def __rpc_get_network_configuration(
|
||||
self,
|
||||
stream: "grpclib.server.Stream[GetNetworkConfigurationRequest, GetNetworkConfigurationResponse]",
|
||||
) -> None:
|
||||
request = await stream.recv_message()
|
||||
response = await self.get_network_configuration(request)
|
||||
await stream.send_message(response)
|
||||
|
||||
async def __rpc_set_network_configuration(
|
||||
self,
|
||||
stream: "grpclib.server.Stream[SetNetworkConfigurationRequest, SetNetworkConfigurationResponse]",
|
||||
) -> None:
|
||||
request = await stream.recv_message()
|
||||
response = await self.set_network_configuration(request)
|
||||
await stream.send_message(response)
|
||||
|
||||
async def __rpc_get_network_info(
|
||||
self,
|
||||
stream: "grpclib.server.Stream[GetNetworkInfoRequest, GetNetworkInfoResponse]",
|
||||
) -> None:
|
||||
request = await stream.recv_message()
|
||||
response = await self.get_network_info(request)
|
||||
await stream.send_message(response)
|
||||
|
||||
def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
|
||||
return {
|
||||
"/braiins.bos.v1.NetworkService/GetNetworkConfiguration": grpclib.const.Handler(
|
||||
self.__rpc_get_network_configuration,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
GetNetworkConfigurationRequest,
|
||||
GetNetworkConfigurationResponse,
|
||||
),
|
||||
"/braiins.bos.v1.NetworkService/SetNetworkConfiguration": grpclib.const.Handler(
|
||||
self.__rpc_set_network_configuration,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
SetNetworkConfigurationRequest,
|
||||
SetNetworkConfigurationResponse,
|
||||
),
|
||||
"/braiins.bos.v1.NetworkService/GetNetworkInfo": grpclib.const.Handler(
|
||||
self.__rpc_get_network_info,
|
||||
grpclib.const.Cardinality.UNARY_UNARY,
|
||||
GetNetworkInfoRequest,
|
||||
GetNetworkInfoResponse,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import aiofiles
|
||||
import httpx
|
||||
|
||||
from pyasic import settings
|
||||
@@ -46,6 +49,14 @@ class ePICWebAPI(BaseWebAPI):
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
for retry_cnt in range(settings.get("get_data_retries", 1)):
|
||||
try:
|
||||
if parameters.get("form") is not None:
|
||||
form_data = parameters["form"]
|
||||
form_data.add_field("password", self.pwd)
|
||||
response = await client.post(
|
||||
f"http://{self.ip}:{self.port}/{command}",
|
||||
timeout=5,
|
||||
data=form_data,
|
||||
)
|
||||
if post:
|
||||
response = await client.post(
|
||||
f"http://{self.ip}:{self.port}/{command}",
|
||||
@@ -135,3 +146,22 @@ class ePICWebAPI(BaseWebAPI):
|
||||
|
||||
async def capabilities(self) -> dict:
|
||||
return await self.send_command("capabilities")
|
||||
|
||||
async def system_update(self, file: Path | str, keep_settings: bool = True):
|
||||
"""Perform a system update by uploading a firmware file and sending a
|
||||
command to initiate the update."""
|
||||
|
||||
# calculate the SHA256 checksum of the firmware file
|
||||
sha256_hash = hashlib.sha256()
|
||||
async with aiofiles.open(str(file), "rb") as f:
|
||||
while chunk := await f.read(8192):
|
||||
sha256_hash.update(chunk)
|
||||
checksum = sha256_hash.hexdigest()
|
||||
|
||||
# prepare the multipart/form-data request
|
||||
form_data = aiohttp.FormData()
|
||||
form_data.add_field("checksum", checksum)
|
||||
form_data.add_field("keepsettings", str(keep_settings).lower())
|
||||
form_data.add_field("update.zip", open(file, "rb"), filename="update.zip")
|
||||
|
||||
await self.send_command("systemupdate", form=form_data)
|
||||
|
||||
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]
|
||||
name = "pyasic"
|
||||
version = "0.58.0"
|
||||
version = "0.60.7"
|
||||
description = "A simplified and standardized interface for Bitcoin ASICs."
|
||||
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
||||
repository = "https://github.com/UpstreamData/pyasic"
|
||||
@@ -10,18 +10,19 @@ readme = "README.md"
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
httpx = ">=0.26.0"
|
||||
asyncssh = "^2.14.2"
|
||||
passlib = "^1.7.4"
|
||||
pyaml = "^23.12.0"
|
||||
toml = "^0.10.2"
|
||||
asyncssh = ">=2.17.0"
|
||||
passlib = ">=1.7.4"
|
||||
pyaml = ">=23.12.0"
|
||||
tomli = { version = ">=2.0.1", python = "<3.11" }
|
||||
tomli-w = "1.0.0"
|
||||
betterproto = "2.0.0b6"
|
||||
aiofiles = "^23.2.1"
|
||||
aiofiles = ">=23.2.1"
|
||||
|
||||
[tool.poetry.group.dev]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pre-commit = "^3.1.0"
|
||||
pre-commit = "^3.5.0"
|
||||
isort = "^5.12.0"
|
||||
|
||||
[tool.poetry.group.docs]
|
||||
|
||||
Reference in New Issue
Block a user