Compare commits

..

74 Commits

Author SHA1 Message Date
Brett Rowan
af920c4dda version: bump version number 2024-09-12 17:28:19 -06:00
Brett Rowan
f3d11788ed bug: fix missing await calls
Fixes #201
2024-09-12 17:27:57 -06:00
Brett Rowan
fd0e02af59 feature: add support for BOSMinerT21 2024-09-12 17:19:08 -06:00
Brett Rowan
2a6c51d52c Merge pull request #188 from Ytemiloluwa/marathon
feat: add _get_pools method for marathon miner
2024-09-04 08:38:29 -06:00
ytemiloluwa
2d62e2070b pool: highest priority 2024-09-04 15:21:39 +01:00
ytemiloluwa
b143bd70f0 updated keys 2024-09-03 22:49:55 +01:00
ytemiloluwa
605509c57c updated keys 2024-09-03 21:35:14 +01:00
Brett Rowan
7036137b23 Merge pull request #189 from 1e9abhi1e10/luxminer_firmware 2024-09-02 22:14:15 -06:00
1e9abhi1e10
7a9ff535b4 Refactor upgrade_firmware to maintain bool return type 2024-09-03 09:42:08 +05:30
Brett Rowan
f185bafe2a version: bump version number. 2024-09-02 21:12:56 -06:00
Brett Rowan
ab81d5d020 feature: add some more whatsminer chip counts. 2024-09-02 21:12:37 -06:00
1e9abhi1e10
0965e6489b return status message in upgrade_firmware function 2024-09-02 23:43:17 +05:30
ytemiloluwa
792e1c9cad corrected parsing 2024-09-02 09:27:30 +01:00
Brett Rowan
a6721f971a version: bump version number. 2024-09-01 16:49:26 -06:00
Brett Rowan
8113d0e4e0 bug: remove print statement. 2024-09-01 16:49:07 -06:00
Brett Rowan
e3c7d3f8a2 version: bump version number. 2024-09-01 16:48:15 -06:00
Brett Rowan
6415de8c73 bug: fix more parsing issues. 2024-09-01 16:47:53 -06:00
Brett Rowan
f2838cf31d bug: fix avalon nano parsing. 2024-09-01 16:41:28 -06:00
Brett Rowan
fbd49b370d version: bump version number. 2024-09-01 16:23:33 -06:00
Brett Rowan
79f7296576 bug: fix some issues with avalonminer parsing. 2024-09-01 16:22:59 -06:00
Brett Rowan
76f4ca5f89 version: bump version number. 2024-09-01 13:22:32 -06:00
Brett Rowan
477acda1c1 feature: add support for Avalon Nano 3. 2024-09-01 13:21:53 -06:00
Brett Rowan
a57f343dcc version: bump version number. 2024-09-01 10:05:01 -06:00
Brett Rowan
36e9201ed4 bug: fix false positives as VNish before checking other firmware types. 2024-09-01 10:04:31 -06:00
Brett Rowan
c1525501d4 Merge pull request #190 from UpstreamData/dev_iceriver
feature: Add iceriver support
2024-09-01 09:53:39 -06:00
Brett Rowan
e4bb90a569 Merge pull request #187 from 1e9abhi1e10/antminer_firmware
feat: Add update firmware for Antminer
2024-09-01 09:52:02 -06:00
1e9abhi1e10
28642cc521 Refactor firmware upgrade process 2024-08-27 02:34:23 +05:30
Brett Rowan
beae79ddec version: bump version number. 2024-08-25 10:36:32 -06:00
Brett Rowan
f02e10ab3d bug: fix token not needing to be passed to upgrade wm firmware. 2024-08-25 10:36:11 -06:00
Brett Rowan
d0b9dff476 version: bump version number. 2024-08-25 09:28:46 -06:00
Brett Rowan
501e290839 bug: fix a bug where whatsminers could raise a cascading TimeoutError. 2024-08-25 09:28:27 -06:00
Brett Rowan
a0daf37f80 bug: fix some miners not having matching params due to inheritance. 2024-08-25 09:28:17 -06:00
Brett Rowan
8111b1ff4b refactor: simplify some bad code in scan_network_generator 2024-08-25 09:13:02 -06:00
Brett Rowan
754087afd6 Merge pull request #195 from Ytemiloluwa/CGMiner
feat: Add _get_pools method for CGMiner (StockFirmware)
2024-08-23 14:57:35 -06:00
ytemiloluwa
5e16b6092c backends: add _get_pools for CGMiner 2024-08-24 00:51:10 +04:00
1e9abhi1e10
21636a75fa made upgrade_firmware to return boolean result 2024-08-23 12:45:20 +05:30
Brett Rowan
f124f5422a Merge pull request #194 from jameshilliard/remove-struct 2024-08-22 17:55:52 -06:00
James Hilliard
1e5d1a2528 Cleanup unused struct import 2024-08-22 17:53:54 -06:00
Brett Rowan
1fcef07902 version: bump version number. 2024-08-22 17:39:27 -06:00
Brett Rowan
41e7dd8056 docs: update docs. 2024-08-22 17:39:01 -06:00
Brett Rowan
dccc35db5f Merge pull request #193 from jameshilliard/remove-solinger
Remove horribly broken SO_LINGER socket options
2024-08-22 17:38:18 -06:00
James Hilliard
0cfe59aa34 Remove horribly broken SO_LINGER socket options 2024-08-22 17:35:16 -06:00
1e9abhi1e10
6fdd156fa3 Added upgraderun in rpc/luxminer 2024-08-21 01:06:50 +05:30
Upstream Data
e9fcf25ad3 docs: update docs with iceriver support. 2024-08-20 10:24:06 -06:00
Brett Rowan
a9422165ca Merge pull request #191 from UpstreamData/dev_mara
feature: add mara rpc API.
2024-08-20 10:21:20 -06:00
Upstream Data
0ea5ee8239 feature: add more iceriver functionality. 2024-08-20 10:16:35 -06:00
Upstream Data
fba25cba61 feature: add a couple iceriver data gathering functions. 2024-08-20 09:59:33 -06:00
Upstream Data
343b5a1c50 feature: add basic iceriver framework. 2024-08-20 09:45:31 -06:00
Upstream Data
63522aad81 feature: add mara rpc API. 2024-08-20 08:37:40 -06:00
1e9abhi1e10
b957aa7fba feat: Add update firmware for LuxOS Miner 2024-08-20 16:12:33 +05:30
ytemiloluwa
a71aa6868a backends: add _get_pools to marathon 2024-08-19 07:08:22 +01:00
1e9abhi1e10
6b50bf0cf7 Fix some minor issues 2024-08-18 01:45:03 +05:30
1e9abhi1e10
d00444ec56 feat: Add update firmware for Antminer 2024-08-18 01:18:42 +05:30
Brett Rowan
e7ed39fe39 Merge pull request #157 from 1e9abhi1e10/update_firmware_2 2024-08-13 12:03:38 -06:00
Upstream Data
168d68d0b2 version: fix bad version number. 2024-08-13 09:24:37 -06:00
Upstream Data
63cddfdde3 version: bump version number. 2024-08-13 09:13:49 -06:00
JP Compagnone
4a642fd3da add s21 pro support for stock, ePIC (#186) 2024-08-09 10:18:14 -06:00
Brett Rowan
13c0407b2d Merge pull request #185 from jameshilliard/toml
Use builtin tomllib where possible and tomli{-w} where not.
2024-08-08 08:18:35 -06:00
James Hilliard
794ed6d103 Use builtin tomllib where possible and tomli{-w} where not. 2024-08-07 15:19:22 -06:00
1e9abhi1e10
962a328219 Fixes result check 2024-07-31 13:23:37 +05:30
1e9abhi1e10
4b4670201a Backed some previous changes 2024-07-27 23:58:57 +05:30
1e9abhi1e10
92f70c9a76 Add keep_settings arg and other reviews 2024-07-27 23:55:50 +05:30
1e9abhi1e10
8664b53991 Fix request handling 2024-07-26 05:30:42 +05:30
Abhishek Patidar
31aeca2340 Merge branch 'master' into update_firmware_2 2024-07-26 05:27:30 +05:30
1e9abhi1e10
ae3d38603a Removed unused import 2024-06-29 14:29:48 +05:30
1e9abhi1e10
e649348af2 used web attribute and removed some redundant changes from the firmware.py 2024-06-27 03:43:33 +05:30
1e9abhi1e10
ba58e80ec3 Add keep_setting arg in miners/base.py/BaseMiner/upgrade_firmware 2024-06-27 02:21:24 +05:30
1e9abhi1e10
45e2c9a403 modified upgrade_firmware for all subclasses 2024-06-26 19:06:51 +05:30
Abhishek Patidar
ba69a1de2c Merge branch 'master' into update_firmware_2 2024-06-22 07:57:48 +05:30
1e9abhi1e10
999e8ef318 Made consistent API calls 2024-06-15 17:44:20 +05:30
1e9abhi1e10
eefb055a3f Fixed upgrade_firmware for auradine miner 2024-06-07 02:58:09 +05:30
1e9abhi1e10
9c41a6b28f Undo changes for ePIC and BFG miners 2024-06-07 02:21:41 +05:30
1e9abhi1e10
bf0e2e6cfe feat: Add update firmware for BFG miner 2024-06-07 02:12:48 +05:30
1e9abhi1e10
4a2adabe95 feat: Add update firmware for Auradine and ePIC miners 2024-06-07 02:04:16 +05:30
56 changed files with 818 additions and 76 deletions

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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>

View File

@@ -24,7 +24,6 @@ Settings options:
- `default_hive_web_password`
- `default_antminer_ssh_password`
- `default_bosminer_ssh_password`
- `socket_linger_time`
### get

25
poetry.lock generated
View File

@@ -1067,14 +1067,25 @@ files = [
]
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
python-versions = ">=3.7"
files = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "tomli-w"
version = "1.0.0"
description = "A lil' TOML writer"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli_w-1.0.0-py3-none-any.whl", hash = "sha256:9f2a07e8be30a0729e533ec968016807069991ae2fd921a78d42f429ae5f4463"},
{file = "tomli_w-1.0.0.tar.gz", hash = "sha256:f463434305e0336248cac9c2dc8076b707d8a12d019dd349f5c1e382dd1ae1b9"},
]
[[package]]
@@ -1184,4 +1195,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools",
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "be96d28fb2d257294061aa04fa3046c5e2a7c672275f819cb253b366b52554b3"
content-hash = "05cf32d9e1c66d2090f3506be3e5065f810c1761ce69d2bc6956db1ba8f30548"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -15,3 +15,4 @@
# ------------------------------------------------------------------------------
from .S21 import BOSMinerS21
from .T21 import BOSMinerT21

View File

@@ -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

View File

@@ -14,9 +14,7 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .S21 import (
ePICS21,
)
from .S21 import ePICS21, ePICS21Pro
from .T21 import (
ePICT21,

View File

@@ -20,3 +20,4 @@ from .A9X import *
from .A10X import *
from .A11X import *
from .A12X import *
from .nano import *

View 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

View File

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

View File

@@ -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

View File

@@ -15,6 +15,8 @@
# ------------------------------------------------------------------------------
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
@@ -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:
@@ -427,6 +457,10 @@ ANTMINER_OLD_DATA_LOC = DataLocations(
"_get_uptime",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_pools", "pools")],
),
}
)

View File

@@ -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}) ###
##################################################

View File

@@ -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

View File

@@ -228,4 +228,4 @@ class BFGMiner(StockFirmware):
expected_rate, HashUnit.SHA256.from_str(rate_unit)
).into(self.algo.unit.default)
except LookupError:
pass
pass

View File

@@ -20,7 +20,12 @@ 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
@@ -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
@@ -191,7 +196,7 @@ class BOSMiner(BraiinsOSFirmware):
self.config = config
parsed_cfg = config.as_bosminer(user_suffix=user_suffix)
toml_conf = toml.dumps(
toml_conf = tomli_w.dumps(
{
"format": {
"version": "2.0",
@@ -722,9 +727,8 @@ BOSER_DATA_LOC = DataLocations(
[RPCAPICommand("rpc_summary", "summary")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[WebAPICommand("grpc_pool_groups", "get_pool_groups")]
)
"_get_pools", [WebAPICommand("grpc_pool_groups", "get_pool_groups")]
),
}
)
@@ -947,7 +951,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
@@ -979,7 +983,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
@@ -1087,7 +1091,7 @@ class BOSer(BraiinsOSFirmware):
get_failures=pool_info["stats"]["stale_shares"],
remote_failures=0,
active=pool_info["active"],
alive=pool_info["alive"]
alive=pool_info["alive"],
)
pools_data.append(pool_data)

View File

@@ -655,13 +655,12 @@ class BTMiner(StockFirmware):
except LookupError:
pass
async def upgrade_firmware(self, file: Path, token: str):
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.

View File

@@ -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

View 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

View File

@@ -14,6 +14,7 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from typing import List, Optional
import logging
from pyasic.config import MinerConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
@@ -146,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}) ###
##################################################

View File

@@ -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

View File

@@ -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)

View File

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

View File

@@ -48,3 +48,7 @@ class ePICMake(BaseMiner):
class BitAxeMake(BaseMiner):
make = MinerMake.BITAXE
class IceRiverMake(BaseMiner):
make = MinerMake.BITAXE

View File

@@ -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 *

View File

@@ -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

View File

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

View File

@@ -20,3 +20,4 @@ from .A9X import *
from .A10X import *
from .A11X import *
from .A12X import *
from .nano import *

View File

@@ -0,0 +1 @@
from .nano3 import AvalonNano3

View 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

View 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

View File

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

View File

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

View File

@@ -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

View File

@@ -51,3 +51,5 @@ class M50SVH40(WhatsMinerMake):
class M50SVH50(WhatsMinerMake):
raw_model = MinerModel.WHATSMINER.M50SVH50
expected_chips = 135

View File

@@ -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), {}),
@@ -372,6 +376,7 @@ MINER_CLASSES = {
"ANTMINER S19 PRO+ HYD.": BOSMinerS19ProPlusHydro,
"ANTMINER T19": BOSMinerT19,
"ANTMINER S21": BOSMinerS21,
"ANTMINER T21": BOSMinerT21,
},
MinerTypes.VNISH: {
None: VNish,
@@ -401,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,
@@ -449,6 +455,10 @@ MINER_CLASSES = {
"BM1366": BitAxeUltra,
"BM1397": BitAxeMax,
},
MinerTypes.ICERIVER: {
None: type("IceRiverUnknown", (IceRiver, IceRiverMake), {}),
"KS2": IceRiverKS2,
},
}
@@ -621,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"]
@@ -687,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:
@@ -707,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,
@@ -796,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:]}"
@@ -896,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

View File

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

View File

@@ -0,0 +1,6 @@
from pyasic.miners.backends.iceriver import IceRiver
from pyasic.miners.device.models import KS2
class IceRiverKS2(IceRiver, KS2):
pass

View File

@@ -0,0 +1 @@
from .KS2 import IceRiverKS2

View File

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

View File

@@ -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:

View File

@@ -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:]}"

View File

@@ -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
View 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")

View File

@@ -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:

View File

@@ -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

View File

@@ -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
)

77
pyasic/web/iceriver.py Normal file
View 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")

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyasic"
version = "0.59.2"
version = "0.60.5"
description = "A simplified and standardized interface for Bitcoin ASICs."
authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic"
@@ -13,7 +13,8 @@ httpx = ">=0.26.0"
asyncssh = ">=2.14.2"
passlib = ">=1.7.4"
pyaml = ">=23.12.0"
toml = ">=0.10.2"
tomli = { version = ">=2.0.1", python = "<3.11" }
tomli-w = "1.0.0"
betterproto = ">=2.0.0b6"
aiofiles = ">=23.2.1"