Compare commits

...

13 Commits

Author SHA1 Message Date
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
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
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
31 changed files with 507 additions and 14 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

@@ -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,12 +118,13 @@ 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 = {}
try:
for key, val in [tuple(item) for item in data_list]:
print(key, val)
data_dict[key] = val
except ValueError:
# --avalon args
@@ -220,7 +221,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

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

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

@@ -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), {}),
@@ -451,6 +454,10 @@ MINER_CLASSES = {
"BM1366": BitAxeUltra,
"BM1397": BitAxeMax,
},
MinerTypes.ICERIVER: {
None: type("IceRiverUnknown", (IceRiver, IceRiverMake), {}),
"KS2": IceRiverKS2,
},
}
@@ -623,6 +630,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 +698,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 +716,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,
@@ -898,10 +907,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 == "AVALONNANO":
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

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