Compare commits

...

37 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
1e9abhi1e10
21636a75fa made upgrade_firmware to return boolean result 2024-08-23 12:45:20 +05:30
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
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
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
40 changed files with 623 additions and 29 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

@@ -284,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
@@ -339,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
@@ -348,3 +356,4 @@ class MinerModel:
AURADINE = AuradineModels
EPIC = ePICModels
BITAXE = BitAxeModels
ICERIVER = IceRiverModels

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

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

View File

@@ -118,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 = {}
@@ -147,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
@@ -220,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
@@ -256,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
@@ -272,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
@@ -287,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
@@ -308,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
@@ -326,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

@@ -21,6 +21,7 @@ from typing import List, Optional, Union
import aiofiles
import tomli_w
try:
import tomllib
except ImportError:
@@ -726,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")]
),
}
)
@@ -951,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
@@ -983,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
@@ -1091,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

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

@@ -9,6 +9,7 @@ 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(
**{
@@ -60,6 +61,10 @@ MARA_DATA_LOC = DataLocations(
"_get_uptime",
[WebAPICommand("web_brief", "brief")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[WebAPICommand("web_pools", "pools")],
),
}
)
@@ -305,3 +310,40 @@ class MaraMiner(MaraFirmware):
return web_miner_config["mode"]["concorde"]["power-target"]
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

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

@@ -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 = {
@@ -330,6 +332,7 @@ MINER_CLASSES = {
"AVALONMINER 1066": CGMinerAvalon1066,
"AVALONMINER 1166PRO": CGMinerAvalon1166Pro,
"AVALONMINER 1246": CGMinerAvalon1246,
"AVALONMINER NANO3": CGMinerAvalonNano3,
},
MinerTypes.INNOSILICON: {
None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}),
@@ -373,6 +376,7 @@ MINER_CLASSES = {
"ANTMINER S19 PRO+ HYD.": BOSMinerS19ProPlusHydro,
"ANTMINER T19": BOSMinerT19,
"ANTMINER S21": BOSMinerS21,
"ANTMINER T21": BOSMinerT21,
},
MinerTypes.VNISH: {
None: VNish,
@@ -451,6 +455,10 @@ MINER_CLASSES = {
"BM1366": BitAxeUltra,
"BM1397": BitAxeMax,
},
MinerTypes.ICERIVER: {
None: type("IceRiverUnknown", (IceRiver, IceRiverMake), {}),
"KS2": IceRiverKS2,
},
}
@@ -623,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"]
@@ -689,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:
@@ -709,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,
@@ -798,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:]}"
@@ -898,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

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

View File

@@ -37,6 +37,7 @@ _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",
}

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.6"
version = "0.60.5"
description = "A simplified and standardized interface for Bitcoin ASICs."
authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic"