Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6721f971a | ||
|
|
8113d0e4e0 | ||
|
|
e3c7d3f8a2 | ||
|
|
6415de8c73 | ||
|
|
f2838cf31d | ||
|
|
fbd49b370d | ||
|
|
79f7296576 | ||
|
|
76f4ca5f89 | ||
|
|
477acda1c1 | ||
|
|
a57f343dcc | ||
|
|
36e9201ed4 | ||
|
|
c1525501d4 | ||
|
|
e4bb90a569 | ||
|
|
28642cc521 | ||
|
|
beae79ddec | ||
|
|
f02e10ab3d | ||
|
|
e9fcf25ad3 | ||
|
|
0ea5ee8239 | ||
|
|
fba25cba61 | ||
|
|
343b5a1c50 | ||
|
|
6b50bf0cf7 | ||
|
|
d00444ec56 |
@@ -51,6 +51,8 @@ def backend_str(backend: MinerTypes) -> str:
|
|||||||
return "Mara Firmware Miners"
|
return "Mara Firmware Miners"
|
||||||
case MinerTypes.BITAXE:
|
case MinerTypes.BITAXE:
|
||||||
return "Stock Firmware BitAxe Miners"
|
return "Stock Firmware BitAxe Miners"
|
||||||
|
case MinerTypes.ICERIVER:
|
||||||
|
return "Stock Firmware IceRiver Miners"
|
||||||
|
|
||||||
|
|
||||||
def create_url_str(mtype: str):
|
def create_url_str(mtype: str):
|
||||||
|
|||||||
@@ -225,6 +225,13 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19 Pro+ Hydro (BOS+)
|
||||||
|
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19ProPlusHydro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## T19 (BOS+)
|
## T19 (BOS+)
|
||||||
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
|
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
|
||||||
handler: python
|
handler: python
|
||||||
@@ -281,6 +288,13 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19 Pro Hydro (VNish)
|
||||||
|
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19ProHydro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## T19 (VNish)
|
## T19 (VNish)
|
||||||
::: pyasic.miners.antminer.vnish.X19.T19.VNishT19
|
::: pyasic.miners.antminer.vnish.X19.T19.VNishT19
|
||||||
handler: python
|
handler: python
|
||||||
|
|||||||
@@ -8,6 +8,13 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## S21 Pro (Stock)
|
||||||
|
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Pro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## T21 (Stock)
|
## T21 (Stock)
|
||||||
::: pyasic.miners.antminer.bmminer.X21.T21.BMMinerT21
|
::: pyasic.miners.antminer.bmminer.X21.T21.BMMinerT21
|
||||||
handler: python
|
handler: python
|
||||||
@@ -36,6 +43,13 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## S21 Pro (ePIC)
|
||||||
|
::: pyasic.miners.antminer.epic.X21.S21.ePICS21Pro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## T21 (ePIC)
|
## T21 (ePIC)
|
||||||
::: pyasic.miners.antminer.epic.X21.T21.ePICT21
|
::: pyasic.miners.antminer.epic.X21.T21.ePICT21
|
||||||
handler: python
|
handler: python
|
||||||
|
|||||||
10
docs/miners/iceriver/KSX.md
Normal file
10
docs/miners/iceriver/KSX.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# pyasic
|
||||||
|
## KSX Models
|
||||||
|
|
||||||
|
## KS2 (Stock)
|
||||||
|
::: pyasic.miners.iceriver.iceminer.KSX.KS2.IceRiverKS2
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
@@ -89,6 +89,7 @@ details {
|
|||||||
<summary>X21 Series:</summary>
|
<summary>X21 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
|
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21-pro-stock">S21 Pro (Stock)</a></li>
|
||||||
<li><a href="../antminer/X21#t21-stock">T21 (Stock)</a></li>
|
<li><a href="../antminer/X21#t21-stock">T21 (Stock)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -461,6 +462,7 @@ details {
|
|||||||
<li><a href="../antminer/X19#s19k-pro-no-pic-bos_1">S19k Pro No PIC (BOS+)</a></li>
|
<li><a href="../antminer/X19#s19k-pro-no-pic-bos_1">S19k Pro No PIC (BOS+)</a></li>
|
||||||
<li><a href="../antminer/X19#s19k-pro-no-pic-bos_1">S19k Pro No PIC (BOS+)</a></li>
|
<li><a href="../antminer/X19#s19k-pro-no-pic-bos_1">S19k Pro No PIC (BOS+)</a></li>
|
||||||
<li><a href="../antminer/X19#s19-xp-bos_1">S19 XP (BOS+)</a></li>
|
<li><a href="../antminer/X19#s19-xp-bos_1">S19 XP (BOS+)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19-pro_1-hydro-bos_1">S19 Pro+ Hydro (BOS+)</a></li>
|
||||||
<li><a href="../antminer/X19#t19-bos_1">T19 (BOS+)</a></li>
|
<li><a href="../antminer/X19#t19-bos_1">T19 (BOS+)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -505,6 +507,7 @@ details {
|
|||||||
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#s19a-vnish">S19a (VNish)</a></li>
|
<li><a href="../antminer/X19#s19a-vnish">S19a (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#s19a-pro-vnish">S19a Pro (VNish)</a></li>
|
<li><a href="../antminer/X19#s19a-pro-vnish">S19a Pro (VNish)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19-pro-hydro-vnish">S19 Pro Hydro (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#t19-vnish">T19 (VNish)</a></li>
|
<li><a href="../antminer/X19#t19-vnish">T19 (VNish)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -535,6 +538,7 @@ details {
|
|||||||
<summary>X21 Series:</summary>
|
<summary>X21 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="../antminer/X21#s21-epic">S21 (ePIC)</a></li>
|
<li><a href="../antminer/X21#s21-epic">S21 (ePIC)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21-pro-epic">S21 Pro (ePIC)</a></li>
|
||||||
<li><a href="../antminer/X21#t21-epic">T21 (ePIC)</a></li>
|
<li><a href="../antminer/X21#t21-epic">T21 (ePIC)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -650,4 +654,15 @@ details {
|
|||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>Stock Firmware IceRiver Miners:</summary>
|
||||||
|
<ul>
|
||||||
|
<details>
|
||||||
|
<summary>KSX Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../iceriver/KSX#ks2-stock">KS2 (Stock)</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -284,6 +284,7 @@ class AvalonminerModels(str, Enum):
|
|||||||
Avalon1066 = "Avalon 1066"
|
Avalon1066 = "Avalon 1066"
|
||||||
Avalon1166Pro = "Avalon 1166 Pro"
|
Avalon1166Pro = "Avalon 1166 Pro"
|
||||||
Avalon1246 = "Avalon 1246"
|
Avalon1246 = "Avalon 1246"
|
||||||
|
AvalonNano3 = "Avalon Nano 3"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
@@ -339,6 +340,13 @@ class BitAxeModels(str, Enum):
|
|||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
class IceRiverModels(str, Enum):
|
||||||
|
KS2 = "KS2"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class MinerModel:
|
class MinerModel:
|
||||||
ANTMINER = AntminerModels
|
ANTMINER = AntminerModels
|
||||||
WHATSMINER = WhatsminerModels
|
WHATSMINER = WhatsminerModels
|
||||||
@@ -348,3 +356,4 @@ class MinerModel:
|
|||||||
AURADINE = AuradineModels
|
AURADINE = AuradineModels
|
||||||
EPIC = ePICModels
|
EPIC = ePICModels
|
||||||
BITAXE = BitAxeModels
|
BITAXE = BitAxeModels
|
||||||
|
ICERIVER = IceRiverModels
|
||||||
|
|||||||
@@ -20,3 +20,4 @@ from .A9X import *
|
|||||||
from .A10X import *
|
from .A10X import *
|
||||||
from .A11X import *
|
from .A11X import *
|
||||||
from .A12X import *
|
from .A12X import *
|
||||||
|
from .nano import *
|
||||||
|
|||||||
17
pyasic/miners/avalonminer/cgminer/nano/__init__.py
Normal file
17
pyasic/miners/avalonminer/cgminer/nano/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from .nano3 import CGMinerAvalonNano3
|
||||||
22
pyasic/miners/avalonminer/cgminer/nano/nano3.py
Normal file
22
pyasic/miners/avalonminer/cgminer/nano/nano3.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from pyasic.miners.backends import AvalonMiner
|
||||||
|
from pyasic.miners.device.models import AvalonNano3
|
||||||
|
|
||||||
|
|
||||||
|
class CGMinerAvalonNano3(AvalonMiner, AvalonNano3):
|
||||||
|
pass
|
||||||
@@ -24,6 +24,7 @@ from .cgminer import CGMiner
|
|||||||
from .epic import ePIC
|
from .epic import ePIC
|
||||||
from .goldshell import GoldshellMiner
|
from .goldshell import GoldshellMiner
|
||||||
from .hiveon import Hiveon
|
from .hiveon import Hiveon
|
||||||
|
from .iceriver import IceRiver
|
||||||
from .innosilicon import Innosilicon
|
from .innosilicon import Innosilicon
|
||||||
from .luxminer import LUXMiner
|
from .luxminer import LUXMiner
|
||||||
from .marathon import MaraMiner
|
from .marathon import MaraMiner
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from typing import List, Optional, Union
|
from typing import List, Optional, Union
|
||||||
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
|
||||||
from pyasic.config import MinerConfig, MiningModeConfig
|
from pyasic.config import MinerConfig, MiningModeConfig
|
||||||
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
|
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
|
||||||
@@ -124,6 +126,34 @@ class AntminerModern(BMMiner):
|
|||||||
# break
|
# break
|
||||||
# await asyncio.sleep(1)
|
# await asyncio.sleep(1)
|
||||||
|
|
||||||
|
async def upgrade_firmware(self, file: Path, keep_settings: bool = True) -> str:
|
||||||
|
"""
|
||||||
|
Upgrade the firmware of the AntMiner device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file (Path): Path to the firmware file.
|
||||||
|
keep_settings (bool): Whether to keep the current settings after the update.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Result of the upgrade process.
|
||||||
|
"""
|
||||||
|
if not file:
|
||||||
|
raise ValueError("File location must be provided for firmware upgrade.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = await self.web.update_firmware(file=file, keep_settings=keep_settings)
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
logging.info("Firmware upgrade process completed successfully for AntMiner.")
|
||||||
|
return "Firmware upgrade completed successfully."
|
||||||
|
else:
|
||||||
|
error_message = result.get("message", "Unknown error")
|
||||||
|
logging.error(f"Firmware upgrade failed. Response: {error_message}")
|
||||||
|
return f"Firmware upgrade failed. Response: {error_message}"
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"An error occurred during the firmware upgrade process: {e}", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
data = await self.web.blink(blink=True)
|
data = await self.web.blink(blink=True)
|
||||||
if data:
|
if data:
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class AvalonMiner(CGMiner):
|
|||||||
stats_items = []
|
stats_items = []
|
||||||
stats_dict = {}
|
stats_dict = {}
|
||||||
for item in _stats_items:
|
for item in _stats_items:
|
||||||
if ":" in item:
|
if ": " in item:
|
||||||
data = item.replace("]", "").split("[")
|
data = item.replace("]", "").split("[")
|
||||||
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
|
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
|
||||||
data_dict = {}
|
data_dict = {}
|
||||||
@@ -147,10 +147,7 @@ class AvalonMiner(CGMiner):
|
|||||||
if raw_data[0] == "":
|
if raw_data[0] == "":
|
||||||
raw_data = raw_data[1:]
|
raw_data = raw_data[1:]
|
||||||
|
|
||||||
if len(raw_data) == 2:
|
stats_dict[raw_data[0]] = raw_data[1:]
|
||||||
stats_dict[raw_data[0]] = raw_data[1]
|
|
||||||
else:
|
|
||||||
stats_dict[raw_data[0]] = raw_data[1:]
|
|
||||||
stats_items.append(raw_data)
|
stats_items.append(raw_data)
|
||||||
|
|
||||||
return stats_dict
|
return stats_dict
|
||||||
@@ -220,7 +217,7 @@ class AvalonMiner(CGMiner):
|
|||||||
try:
|
try:
|
||||||
board_hr = parsed_stats["MGHS"][board]
|
board_hr = parsed_stats["MGHS"][board]
|
||||||
hashboards[board].hashrate = AlgoHashRate.SHA256(
|
hashboards[board].hashrate = AlgoHashRate.SHA256(
|
||||||
board_hr, HashUnit.SHA256.GH
|
float(board_hr), HashUnit.SHA256.GH
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
@@ -256,7 +253,7 @@ class AvalonMiner(CGMiner):
|
|||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
return AlgoHashRate.SHA256(
|
return AlgoHashRate.SHA256(
|
||||||
parsed_stats["GHSmm"], HashUnit.SHA256.GH
|
float(parsed_stats["GHSmm"][0]), HashUnit.SHA256.GH
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default)
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
@@ -272,7 +269,7 @@ class AvalonMiner(CGMiner):
|
|||||||
try:
|
try:
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
return float(parsed_stats["Temp"])
|
return float(parsed_stats["Temp"][0])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -287,7 +284,7 @@ class AvalonMiner(CGMiner):
|
|||||||
try:
|
try:
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
return int(parsed_stats["MPO"])
|
return int(parsed_stats["MPO"][0])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -308,7 +305,7 @@ class AvalonMiner(CGMiner):
|
|||||||
|
|
||||||
for fan in range(self.expected_fans):
|
for fan in range(self.expected_fans):
|
||||||
try:
|
try:
|
||||||
fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"])
|
fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"][0])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
return fans_data
|
return fans_data
|
||||||
@@ -326,7 +323,7 @@ class AvalonMiner(CGMiner):
|
|||||||
try:
|
try:
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
led = int(parsed_stats["Led"])
|
led = int(parsed_stats["Led"][0])
|
||||||
return True if led == 1 else False
|
return True if led == 1 else False
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -655,13 +655,12 @@ class BTMiner(StockFirmware):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def upgrade_firmware(self, file: Path, token: str):
|
async def upgrade_firmware(self, file: Path):
|
||||||
"""
|
"""
|
||||||
Upgrade the firmware of the Whatsminer device.
|
Upgrade the firmware of the Whatsminer device.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file (Path): The local file path of the firmware to be uploaded.
|
file (Path): The local file path of the firmware to be uploaded.
|
||||||
token (str): The authentication token for the firmware upgrade.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Confirmation message after upgrading the firmware.
|
str: Confirmation message after upgrading the firmware.
|
||||||
|
|||||||
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
|
||||||
@@ -48,3 +48,7 @@ class ePICMake(BaseMiner):
|
|||||||
|
|
||||||
class BitAxeMake(BaseMiner):
|
class BitAxeMake(BaseMiner):
|
||||||
make = MinerMake.BITAXE
|
make = MinerMake.BITAXE
|
||||||
|
|
||||||
|
|
||||||
|
class IceRiverMake(BaseMiner):
|
||||||
|
make = MinerMake.BITAXE
|
||||||
|
|||||||
@@ -19,5 +19,6 @@ from .auradine import *
|
|||||||
from .avalonminer import *
|
from .avalonminer import *
|
||||||
from .epic import *
|
from .epic import *
|
||||||
from .goldshell import *
|
from .goldshell import *
|
||||||
|
from .iceriver import *
|
||||||
from .innosilicon import *
|
from .innosilicon import *
|
||||||
from .whatsminer import *
|
from .whatsminer import *
|
||||||
|
|||||||
@@ -20,3 +20,4 @@ from .A9X import *
|
|||||||
from .A10X import *
|
from .A10X import *
|
||||||
from .A11X import *
|
from .A11X import *
|
||||||
from .A12X import *
|
from .A12X import *
|
||||||
|
from .nano import *
|
||||||
|
|||||||
1
pyasic/miners/device/models/avalonminer/nano/__init__.py
Normal file
1
pyasic/miners/device/models/avalonminer/nano/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .nano3 import AvalonNano3
|
||||||
10
pyasic/miners/device/models/avalonminer/nano/nano3.py
Normal file
10
pyasic/miners/device/models/avalonminer/nano/nano3.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from pyasic.device import MinerModel
|
||||||
|
from pyasic.miners.device.makes import AvalonMinerMake
|
||||||
|
|
||||||
|
|
||||||
|
class AvalonNano3(AvalonMinerMake):
|
||||||
|
raw_model = MinerModel.AVALONMINER.AvalonNano3
|
||||||
|
|
||||||
|
expected_hashboards = 1
|
||||||
|
expected_chips = 10
|
||||||
|
expected_fans = 1
|
||||||
23
pyasic/miners/device/models/iceriver/KSX/KS2.py
Normal file
23
pyasic/miners/device/models/iceriver/KSX/KS2.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2024 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
from pyasic.device.models import MinerModel
|
||||||
|
from pyasic.miners.device.makes import IceRiverMake
|
||||||
|
|
||||||
|
|
||||||
|
class KS2(IceRiverMake):
|
||||||
|
raw_model = MinerModel.ICERIVER.KS2
|
||||||
|
|
||||||
|
expected_fans = 4
|
||||||
1
pyasic/miners/device/models/iceriver/KSX/__init__.py
Normal file
1
pyasic/miners/device/models/iceriver/KSX/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .KS2 import KS2
|
||||||
1
pyasic/miners/device/models/iceriver/__init__.py
Normal file
1
pyasic/miners/device/models/iceriver/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .KSX import *
|
||||||
@@ -38,6 +38,7 @@ from pyasic.miners.bitaxe import *
|
|||||||
from pyasic.miners.blockminer import *
|
from pyasic.miners.blockminer import *
|
||||||
from pyasic.miners.device.makes import *
|
from pyasic.miners.device.makes import *
|
||||||
from pyasic.miners.goldshell import *
|
from pyasic.miners.goldshell import *
|
||||||
|
from pyasic.miners.iceriver import *
|
||||||
from pyasic.miners.innosilicon import *
|
from pyasic.miners.innosilicon import *
|
||||||
from pyasic.miners.whatsminer import *
|
from pyasic.miners.whatsminer import *
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ class MinerTypes(enum.Enum):
|
|||||||
AURADINE = 10
|
AURADINE = 10
|
||||||
MARATHON = 11
|
MARATHON = 11
|
||||||
BITAXE = 12
|
BITAXE = 12
|
||||||
|
ICERIVER = 13
|
||||||
|
|
||||||
|
|
||||||
MINER_CLASSES = {
|
MINER_CLASSES = {
|
||||||
@@ -330,6 +332,7 @@ MINER_CLASSES = {
|
|||||||
"AVALONMINER 1066": CGMinerAvalon1066,
|
"AVALONMINER 1066": CGMinerAvalon1066,
|
||||||
"AVALONMINER 1166PRO": CGMinerAvalon1166Pro,
|
"AVALONMINER 1166PRO": CGMinerAvalon1166Pro,
|
||||||
"AVALONMINER 1246": CGMinerAvalon1246,
|
"AVALONMINER 1246": CGMinerAvalon1246,
|
||||||
|
"AVALONMINER NANO3": CGMinerAvalonNano3,
|
||||||
},
|
},
|
||||||
MinerTypes.INNOSILICON: {
|
MinerTypes.INNOSILICON: {
|
||||||
None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}),
|
None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}),
|
||||||
@@ -451,6 +454,10 @@ MINER_CLASSES = {
|
|||||||
"BM1366": BitAxeUltra,
|
"BM1366": BitAxeUltra,
|
||||||
"BM1397": BitAxeMax,
|
"BM1397": BitAxeMax,
|
||||||
},
|
},
|
||||||
|
MinerTypes.ICERIVER: {
|
||||||
|
None: type("IceRiverUnknown", (IceRiver, IceRiverMake), {}),
|
||||||
|
"KS2": IceRiverKS2,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -623,6 +630,8 @@ class MinerFactory:
|
|||||||
return MinerTypes.INNOSILICON
|
return MinerTypes.INNOSILICON
|
||||||
if "Miner UI" in web_text:
|
if "Miner UI" in web_text:
|
||||||
return MinerTypes.AURADINE
|
return MinerTypes.AURADINE
|
||||||
|
if "<TITLE>用户界面</TITLE>" in web_text:
|
||||||
|
return MinerTypes.ICERIVER
|
||||||
|
|
||||||
async def _get_miner_socket(self, ip: str) -> MinerTypes | None:
|
async def _get_miner_socket(self, ip: str) -> MinerTypes | None:
|
||||||
commands = ["version", "devdetails"]
|
commands = ["version", "devdetails"]
|
||||||
@@ -689,8 +698,6 @@ class MinerFactory:
|
|||||||
return MinerTypes.BRAIINS_OS
|
return MinerTypes.BRAIINS_OS
|
||||||
if "BTMINER" in upper_data or "BITMICRO" in upper_data:
|
if "BTMINER" in upper_data or "BITMICRO" in upper_data:
|
||||||
return MinerTypes.WHATSMINER
|
return MinerTypes.WHATSMINER
|
||||||
if "VNISH" in upper_data or "DEVICE PATH" in upper_data:
|
|
||||||
return MinerTypes.VNISH
|
|
||||||
if "HIVEON" in upper_data:
|
if "HIVEON" in upper_data:
|
||||||
return MinerTypes.HIVEON
|
return MinerTypes.HIVEON
|
||||||
if "LUXMINER" in upper_data:
|
if "LUXMINER" in upper_data:
|
||||||
@@ -709,6 +716,8 @@ class MinerFactory:
|
|||||||
return MinerTypes.AVALONMINER
|
return MinerTypes.AVALONMINER
|
||||||
if "GCMINER" in upper_data or "FLUXOS" in upper_data:
|
if "GCMINER" in upper_data or "FLUXOS" in upper_data:
|
||||||
return MinerTypes.AURADINE
|
return MinerTypes.AURADINE
|
||||||
|
if "VNISH" in upper_data or "DEVICE PATH" in upper_data:
|
||||||
|
return MinerTypes.VNISH
|
||||||
|
|
||||||
async def send_web_command(
|
async def send_web_command(
|
||||||
self,
|
self,
|
||||||
@@ -798,7 +807,9 @@ class MinerFactory:
|
|||||||
str_data = str_data.replace("info", "1nfo")
|
str_data = str_data.replace("info", "1nfo")
|
||||||
str_data = str_data.replace("inf", "0")
|
str_data = str_data.replace("inf", "0")
|
||||||
str_data = str_data.replace("1nfo", "info")
|
str_data = str_data.replace("1nfo", "info")
|
||||||
|
str_data = str_data.replace("nano", "n4no")
|
||||||
str_data = str_data.replace("nan", "0")
|
str_data = str_data.replace("nan", "0")
|
||||||
|
str_data = str_data.replace("n4no", "nano")
|
||||||
# fix whatever this garbage from avalonminers is `,"id":1}`
|
# fix whatever this garbage from avalonminers is `,"id":1}`
|
||||||
if str_data.startswith(","):
|
if str_data.startswith(","):
|
||||||
str_data = f"{{{str_data[1:]}"
|
str_data = f"{{{str_data[1:]}"
|
||||||
@@ -898,10 +909,12 @@ class MinerFactory:
|
|||||||
async def get_miner_model_avalonminer(self, ip: str) -> str | None:
|
async def get_miner_model_avalonminer(self, ip: str) -> str | None:
|
||||||
sock_json_data = await self.send_api_command(ip, "version")
|
sock_json_data = await self.send_api_command(ip, "version")
|
||||||
try:
|
try:
|
||||||
miner_model = sock_json_data["VERSION"][0]["PROD"]
|
miner_model = sock_json_data["VERSION"][0]["PROD"].upper()
|
||||||
if "-" in miner_model:
|
if "-" in miner_model:
|
||||||
miner_model = miner_model.split("-")[0]
|
miner_model = miner_model.split("-")[0]
|
||||||
|
if miner_model in ["AVALONNANO", "AVALON0O"]:
|
||||||
|
nano_subtype = sock_json_data["VERSION"][0]["MODEL"].upper()
|
||||||
|
miner_model = f"AVALONMINER {nano_subtype}"
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|||||||
1
pyasic/miners/iceriver/__init__.py
Normal file
1
pyasic/miners/iceriver/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .iceminer import *
|
||||||
6
pyasic/miners/iceriver/iceminer/KSX/KS2.py
Normal file
6
pyasic/miners/iceriver/iceminer/KSX/KS2.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pyasic.miners.backends.iceriver import IceRiver
|
||||||
|
from pyasic.miners.device.models import KS2
|
||||||
|
|
||||||
|
|
||||||
|
class IceRiverKS2(IceRiver, KS2):
|
||||||
|
pass
|
||||||
1
pyasic/miners/iceriver/iceminer/KSX/__init__.py
Normal file
1
pyasic/miners/iceriver/iceminer/KSX/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .KS2 import IceRiverKS2
|
||||||
1
pyasic/miners/iceriver/iceminer/__init__.py
Normal file
1
pyasic/miners/iceriver/iceminer/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .KSX import *
|
||||||
@@ -271,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("info", "1nfo")
|
||||||
str_data = str_data.replace("inf", "0")
|
str_data = str_data.replace("inf", "0")
|
||||||
str_data = str_data.replace("1nfo", "info")
|
str_data = str_data.replace("1nfo", "info")
|
||||||
|
str_data = str_data.replace("nano", "n4no")
|
||||||
str_data = str_data.replace("nan", "0")
|
str_data = str_data.replace("nan", "0")
|
||||||
|
str_data = str_data.replace("n4no", "nano")
|
||||||
|
|
||||||
# fix whatever this garbage from avalonminers is `,"id":1}`
|
# fix whatever this garbage from avalonminers is `,"id":1}`
|
||||||
if str_data.startswith(","):
|
if str_data.startswith(","):
|
||||||
str_data = f"{{{str_data[1:]}"
|
str_data = f"{{{str_data[1:]}"
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ _settings = { # defaults
|
|||||||
"default_auradine_web_password": "admin",
|
"default_auradine_web_password": "admin",
|
||||||
"default_epic_web_password": "letmein",
|
"default_epic_web_password": "letmein",
|
||||||
"default_hive_web_password": "admin",
|
"default_hive_web_password": "admin",
|
||||||
|
"default_iceriver_web_password": "12345678",
|
||||||
"default_antminer_ssh_password": "miner",
|
"default_antminer_ssh_password": "miner",
|
||||||
"default_bosminer_ssh_password": "root",
|
"default_bosminer_ssh_password": "root",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,6 @@ from .base import BaseWebAPI
|
|||||||
from .braiins_os import BOSerWebAPI, BOSMinerWebAPI
|
from .braiins_os import BOSerWebAPI, BOSMinerWebAPI
|
||||||
from .epic import ePICWebAPI
|
from .epic import ePICWebAPI
|
||||||
from .goldshell import GoldshellWebAPI
|
from .goldshell import GoldshellWebAPI
|
||||||
|
from .iceriver import IceRiverWebAPI
|
||||||
from .innosilicon import InnosiliconWebAPI
|
from .innosilicon import InnosiliconWebAPI
|
||||||
from .vnish import VNishWebAPI
|
from .vnish import VNishWebAPI
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
import aiofiles
|
||||||
import httpx
|
import httpx
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from pyasic import settings
|
from pyasic import settings
|
||||||
from pyasic.web.base import BaseWebAPI
|
from pyasic.web.base import BaseWebAPI
|
||||||
@@ -59,9 +60,8 @@ class AntminerModernWebAPI(BaseWebAPI):
|
|||||||
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
|
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
|
||||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(
|
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||||
transport=settings.transport(),
|
|
||||||
) as client:
|
|
||||||
if parameters:
|
if parameters:
|
||||||
data = await client.post(
|
data = await client.post(
|
||||||
url,
|
url,
|
||||||
@@ -71,14 +71,15 @@ class AntminerModernWebAPI(BaseWebAPI):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
data = await client.get(url, auth=auth)
|
data = await client.get(url, auth=auth)
|
||||||
except httpx.HTTPError:
|
except httpx.HTTPError as e:
|
||||||
pass
|
return {"success": False, "message": f"HTTP error occurred: {str(e)}"}
|
||||||
else:
|
else:
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
try:
|
try:
|
||||||
return data.json()
|
return data.json()
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
pass
|
return {"success": False, "message": "Failed to decode JSON"}
|
||||||
|
return {"success": False, "message": "Unknown error occurred"}
|
||||||
|
|
||||||
async def multicommand(
|
async def multicommand(
|
||||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||||
@@ -403,3 +404,20 @@ class AntminerOldWebAPI(BaseWebAPI):
|
|||||||
dict: Information about the mining pools configured in the miner.
|
dict: Information about the mining pools configured in the miner.
|
||||||
"""
|
"""
|
||||||
return await self.send_command("miner_pools")
|
return await self.send_command("miner_pools")
|
||||||
|
|
||||||
|
async def update_firmware(self, file: Path, keep_settings: bool = True) -> dict:
|
||||||
|
"""Perform a system update by uploading a firmware file and sending a command to initiate the update."""
|
||||||
|
|
||||||
|
async with aiofiles.open(file, "rb") as firmware:
|
||||||
|
file_content = await firmware.read()
|
||||||
|
|
||||||
|
parameters = {
|
||||||
|
"file": (file.name, file_content, "application/octet-stream"),
|
||||||
|
"filename": file.name,
|
||||||
|
"keep_settings": keep_settings
|
||||||
|
}
|
||||||
|
|
||||||
|
return await self.send_command(
|
||||||
|
command="upgrade",
|
||||||
|
**parameters
|
||||||
|
)
|
||||||
|
|||||||
77
pyasic/web/iceriver.py
Normal file
77
pyasic/web/iceriver.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2024 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import warnings
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from pyasic import settings
|
||||||
|
from pyasic.errors import APIError
|
||||||
|
from pyasic.web.base import BaseWebAPI
|
||||||
|
|
||||||
|
|
||||||
|
class IceRiverWebAPI(BaseWebAPI):
|
||||||
|
def __init__(self, ip: str) -> None:
|
||||||
|
super().__init__(ip)
|
||||||
|
self.username = "admin"
|
||||||
|
self.pwd = settings.get("default_iceriver_web_password", "12345678")
|
||||||
|
|
||||||
|
async def multicommand(
|
||||||
|
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||||
|
) -> dict:
|
||||||
|
tasks = {c: asyncio.create_task(getattr(self, c)()) for c in commands}
|
||||||
|
await asyncio.gather(*[t for t in tasks.values()])
|
||||||
|
return {t: tasks[t].result() for t in tasks}
|
||||||
|
|
||||||
|
async def send_command(
|
||||||
|
self,
|
||||||
|
command: str | bytes,
|
||||||
|
ignore_errors: bool = False,
|
||||||
|
allow_warning: bool = True,
|
||||||
|
privileged: bool = False,
|
||||||
|
**parameters: Any,
|
||||||
|
) -> dict:
|
||||||
|
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||||
|
try:
|
||||||
|
# auth
|
||||||
|
await client.post(
|
||||||
|
f"http://{self.ip}:{self.port}/user/loginpost",
|
||||||
|
params={"post": "6", "user": self.username, "pwd": self.pwd},
|
||||||
|
)
|
||||||
|
except httpx.HTTPError:
|
||||||
|
warnings.warn(f"Could not authenticate with miner web: {self}")
|
||||||
|
try:
|
||||||
|
resp = await client.post(
|
||||||
|
f"http://{self.ip}:{self.port}/user/{command}", params=parameters
|
||||||
|
)
|
||||||
|
if not resp.status_code == 200:
|
||||||
|
if not ignore_errors:
|
||||||
|
raise APIError(f"Command failed: {command}")
|
||||||
|
warnings.warn(f"Command failed: {command}")
|
||||||
|
return resp.json()
|
||||||
|
except httpx.HTTPError:
|
||||||
|
raise APIError(f"Command failed: {command}")
|
||||||
|
|
||||||
|
async def locate(self, enable: bool):
|
||||||
|
return await self.send_command(
|
||||||
|
"userpanel", post="5", locate="1" if enable else "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def userpanel(self):
|
||||||
|
return await self.send_command("userpanel", post="4")
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pyasic"
|
name = "pyasic"
|
||||||
version = "0.59.5"
|
version = "0.60.3"
|
||||||
description = "A simplified and standardized interface for Bitcoin ASICs."
|
description = "A simplified and standardized interface for Bitcoin ASICs."
|
||||||
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
||||||
repository = "https://github.com/UpstreamData/pyasic"
|
repository = "https://github.com/UpstreamData/pyasic"
|
||||||
|
|||||||
Reference in New Issue
Block a user