Compare commits

..

11 Commits

Author SHA1 Message Date
Brett Rowan
1ea4f4e124 version: bump version number 2025-07-15 08:11:34 -06:00
bbemoll
a8a0e4a5fe Added support for Antminer S19XH Hydro running on Braiins OS (#353)
* Add pyasic files

* Add pyasic files

* Update pyproject.toml

* Update pyproject.toml

* Added support for Antminer S19XP Hydro running on Braiins OS

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-07-15 08:10:59 -06:00
Brett Rowan
5f2f6e01da version: bump version number 2025-07-10 10:56:42 -06:00
Brody
41b4c23d45 Fix API Issue with Bitaxe Miners (#352)
* added checks to identify which field is None.

* better logging

* if asicCount is None, try to find it in /system/asic

* ran pre-commit
2025-07-10 10:56:22 -06:00
Brett Rowan
b4687f18fd version: bump version number 2025-05-01 15:15:42 -06:00
Brett Rowan
2437421005 feature: add support for a bunch of BOS and VNish S21 models. 2025-05-01 15:15:18 -06:00
Ryan Heideman
40ebc2773f Retry and Timeout Changes for espminer (#340)
* Retry and Timeout Fixes for espminer

* Build check fix
2025-05-01 15:07:58 -06:00
Art
b8ae238d23 Fix incorrect computation of active_preset for Antminer VNish. See #338. (#339)
* Fix incorrect computation of active_preset for Antminer VNish. See #338.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* #338 renamed perf_summary to web_perf_summary for clarity and consistency. New API call is done in backward compatibility manner. Data is also retrieved safely

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-05-01 15:07:11 -06:00
Brett Rowan
2920639b70 version: bump version number 2025-04-27 11:01:10 -06:00
Brett Rowan
bd9144b3de requirements: bump dependency versions 2025-04-27 11:01:10 -06:00
SKART1
8f7a67d4dc Fix efficiency_fract property to correctly return computed value. See #334. 2025-04-19 08:28:44 -06:00
21 changed files with 262 additions and 50 deletions

View File

@@ -23,7 +23,7 @@ Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with
## Installation
It is recommended to install `pyasic` in a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/#what-other-popular-options-exist-aside-from-venv) to isolate it from the rest of your system. Options include:
- [pypoetry](https://python-poetry.org/): the reccommended way, since pyasic already uses it by default
- [pypoetry](https://python-poetry.org/): the reccommended way, since pyasic already uses it by default. Use version 2.0+
```
poetry install

View File

@@ -92,6 +92,19 @@
show_root_heading: false
heading_level: 0
## S21 Hydro (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21Hydro
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 Pro (BOS+)
- [x] Shutdowns
@@ -105,6 +118,32 @@
show_root_heading: false
heading_level: 0
## S21+ (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21Plus
handler: python
options:
show_root_heading: false
heading_level: 0
## S21+ Hydro (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21PlusHydro
handler: python
options:
show_root_heading: false
heading_level: 0
## T21 (BOS+)
- [x] Shutdowns
@@ -131,6 +170,58 @@
show_root_heading: false
heading_level: 0
## S21 Hydro (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21Hydro
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 Pro (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21Pro
handler: python
options:
show_root_heading: false
heading_level: 0
## S21+ (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21Plus
handler: python
options:
show_root_heading: false
heading_level: 0
## S21+ Hydro (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21PlusHydro
handler: python
options:
show_root_heading: false
heading_level: 0
## T21 (VNish)
- [x] Shutdowns

View File

@@ -663,6 +663,9 @@ details {
<ul>
<li><a href="../antminer/X21#s21-bos_1">S21 (BOS+)</a></li>
<li><a href="../antminer/X21#s21-pro-bos_1">S21 Pro (BOS+)</a></li>
<li><a href="../antminer/X21#s21_1-bos_1">S21+ (BOS+)</a></li>
<li><a href="../antminer/X21#s21_1-hydro-bos_1">S21+ Hydro (BOS+)</a></li>
<li><a href="../antminer/X21#s21-hydro-bos_1">S21 Hydro (BOS+)</a></li>
<li><a href="../antminer/X21#t21-bos_1">T21 (BOS+)</a></li>
</ul>
</details>
@@ -721,6 +724,10 @@ details {
<ul>
<li><a href="../antminer/X21#t21-vnish">T21 (VNish)</a></li>
<li><a href="../antminer/X21#s21-vnish">S21 (VNish)</a></li>
<li><a href="../antminer/X21#s21_1-vnish">S21+ (VNish)</a></li>
<li><a href="../antminer/X21#s21_1-hydro-vnish">S21+ Hydro (VNish)</a></li>
<li><a href="../antminer/X21#s21-pro-vnish">S21 Pro (VNish)</a></li>
<li><a href="../antminer/X21#s21-hydro-vnish">S21 Hydro (VNish)</a></li>
</ul>
</details>
</ul>

View File

@@ -283,13 +283,17 @@ class MinerConfig(BaseModel):
)
@classmethod
def from_vnish(cls, web_settings: dict, web_presets: list[dict]) -> "MinerConfig":
def from_vnish(
cls, web_settings: dict, web_presets: list[dict], web_perf_summary: dict
) -> "MinerConfig":
"""Constructs a MinerConfig object from web settings for VNish miners."""
return cls(
pools=PoolConfig.from_vnish(web_settings),
fan_mode=FanModeConfig.from_vnish(web_settings),
temperature=TemperatureConfig.from_vnish(web_settings),
mining_mode=MiningModeConfig.from_vnish(web_settings, web_presets),
mining_mode=MiningModeConfig.from_vnish(
web_settings, web_presets, web_perf_summary
),
)
@classmethod

View File

@@ -412,12 +412,18 @@ class MiningModePreset(MinerConfigValue):
@classmethod
def from_vnish(
cls, web_overclock_settings: dict, web_presets: list[dict]
cls,
web_overclock_settings: dict,
web_presets: list[dict],
web_perf_summary: dict,
) -> "MiningModePreset":
active_preset = None
for preset in web_presets:
if preset["name"] == web_overclock_settings["preset"]:
active_preset = preset
active_preset = web_perf_summary.get("current_preset")
if active_preset is None:
for preset in web_presets:
if preset["name"] == web_overclock_settings["preset"]:
active_preset = preset
return cls(
active_preset=MiningPreset.from_vnish(active_preset),
available_presets=[MiningPreset.from_vnish(p) for p in web_presets],
@@ -703,7 +709,9 @@ class MiningModeConfig(MinerConfigOption):
return cls.default()
@classmethod
def from_vnish(cls, web_settings: dict, web_presets: list[dict]):
def from_vnish(
cls, web_settings: dict, web_presets: list[dict], web_perf_summary: dict
):
try:
mode_settings = web_settings["miner"]["overclock"]
except KeyError:
@@ -712,7 +720,9 @@ class MiningModeConfig(MinerConfigOption):
if mode_settings["preset"] == "disabled":
return MiningModeManual.from_vnish(mode_settings)
else:
return MiningModePreset.from_vnish(mode_settings, web_presets)
return MiningModePreset.from_vnish(
mode_settings, web_presets, web_perf_summary
)
@classmethod
def from_boser(cls, grpc_miner_conf: dict):

View File

@@ -70,6 +70,7 @@ class MinerData(BaseModel):
errors: A list of errors on the miner.
fault_light: Whether the fault light is on as a boolean.
efficiency: Efficiency of the miner in J/TH (Watts per TH/s). Calculated automatically.
efficiency_fract: Same as efficiency, but is not rounded to integer. Calculated automatically.
is_mining: Whether the miner is mining.
pools: A list of PoolMetrics instances, each representing metrics for a pool.
"""
@@ -293,7 +294,7 @@ class MinerData(BaseModel):
@computed_field # type: ignore[misc]
@property
def efficiency_fract(self) -> float | None:
self._efficiency(2)
return self._efficiency(2)
def _efficiency(self, ndigits: int) -> float | None:
if self.hashrate is None or self.wattage is None:
@@ -301,7 +302,7 @@ class MinerData(BaseModel):
try:
return round(self.wattage / float(self.hashrate), ndigits)
except ZeroDivisionError:
return 0
return 0.0
@computed_field # type: ignore[misc]
@property

View File

@@ -63,6 +63,7 @@ class AntminerModels(MinerModelType):
S21Pro = "S21 Pro"
S21Hydro = "S21 Hydro"
T21 = "T21"
S19XPHydro = "S19 XP Hydro"
def __str__(self):
return self.value

View File

@@ -30,6 +30,7 @@ from pyasic.miners.device.models import (
S19Plus,
S19Pro,
S19ProPlusHydro,
S19XPHydro,
)
@@ -87,3 +88,7 @@ class BOSMinerS19XP(BOSer, S19XP):
class BOSMinerS19ProPlusHydro(BOSer, S19ProPlusHydro):
pass
class BOSMinerS19XPHydro(BOSer, S19XPHydro):
pass

View File

@@ -29,5 +29,6 @@ from .S19 import (
BOSMinerS19Pro,
BOSMinerS19ProPlusHydro,
BOSMinerS19XP,
BOSMinerS19XPHydro,
)
from .T19 import BOSMinerT19

View File

@@ -15,12 +15,40 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSer
from pyasic.miners.device.models import S21, S21Pro
from pyasic.miners.device.models import S21, S21Hydro, S21Plus, S21PlusHydro, S21Pro
# ------------------------------------------------------------------------------
# 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. -
# ------------------------------------------------------------------------------
class BOSMinerS21(BOSer, S21):
pass
class BOSMinerS21Plus(BOSer, S21Plus):
pass
class BOSMinerS21PlusHydro(BOSer, S21PlusHydro):
pass
class BOSMinerS21Pro(BOSer, S21Pro):
pass
class BOSMinerS21Hydro(BOSer, S21Hydro):
pass

View File

@@ -14,5 +14,11 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .S21 import BOSMinerS21, BOSMinerS21Pro
from .S21 import (
BOSMinerS21,
BOSMinerS21Hydro,
BOSMinerS21Plus,
BOSMinerS21PlusHydro,
BOSMinerS21Pro,
)
from .T21 import BOSMinerT21

View File

@@ -15,8 +15,24 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import VNish
from pyasic.miners.device.models import S21
from pyasic.miners.device.models import S21, S21Hydro, S21Plus, S21PlusHydro, S21Pro
class VNishS21(VNish, S21):
pass
class VNishS21Plus(VNish, S21Plus):
pass
class VNishS21PlusHydro(VNish, S21PlusHydro):
pass
class VNishS21Pro(VNish, S21Pro):
pass
class VNishS21Hydro(VNish, S21Hydro):
pass

View File

@@ -14,5 +14,11 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .S21 import VNishS21
from .S21 import (
VNishS21,
VNishS21Hydro,
VNishS21Plus,
VNishS21PlusHydro,
VNishS21Pro,
)
from .T21 import VNishT21

View File

@@ -115,11 +115,18 @@ class ESPMiner(BaseMiner):
if web_system_info is not None:
try:
expected_hashrate = (
web_system_info.get("smallCoreCount")
* web_system_info.get("asicCount")
* web_system_info.get("frequency")
)
small_core_count = web_system_info.get("smallCoreCount")
asic_count = web_system_info.get("asicCount")
frequency = web_system_info.get("frequency")
if asic_count is None:
try:
asic_info = await self.web.asic_info()
asic_count = asic_info.get("asicCount")
except APIError:
pass
expected_hashrate = small_core_count * asic_count * frequency
return self.algo.hashrate(
rate=float(expected_hashrate), unit=self.algo.unit.MH

View File

@@ -293,9 +293,12 @@ class VNish(VNishFirmware, BMMiner):
try:
web_settings = await self.web.settings()
web_presets = await self.web.autotune_presets()
web_perf_summary = (await self.web.perf_summary()) or {}
except APIError:
return self.config
self.config = MinerConfig.from_vnish(web_settings, web_presets)
self.config = MinerConfig.from_vnish(
web_settings, web_presets, web_perf_summary
)
return self.config
async def set_power_limit(self, wattage: int) -> bool:

View File

@@ -223,3 +223,12 @@ class S19jXP(AntMinerMake):
expected_fans = 4
expected_hashboards = 3
algo = MinerAlgo.SHA256
class S19XPHydro(AntMinerMake):
raw_model = MinerModel.ANTMINER.S19XPHydro
expected_chips = 204
expected_fans = 0
expected_hashboards = 3
algo = MinerAlgo.SHA256

View File

@@ -38,5 +38,6 @@ from .S19 import (
S19ProHydro,
S19ProPlus,
S19ProPlusHydro,
S19XPHydro,
)
from .T19 import T19

View File

@@ -555,9 +555,13 @@ MINER_CLASSES = {
"ANTMINER T19": BOSMinerT19,
"ANTMINER S21": BOSMinerS21,
"ANTMINER S21 PRO": BOSMinerS21Pro,
"ANTMINER S21+": BOSMinerS21Plus,
"ANTMINER S21+ HYD.": BOSMinerS21PlusHydro,
"ANTMINER S21 HYD.": BOSMinerS21Hydro,
"ANTMINER T21": BOSMinerT21,
"BRAIINS MINI MINER BMM 100": BraiinsBMM100,
"BRAIINS MINI MINER BMM 101": BraiinsBMM101,
"ANTMINER S19 XP HYD.": BOSMinerS19XPHydro,
},
MinerTypes.VNISH: {
None: VNish,
@@ -581,6 +585,10 @@ MINER_CLASSES = {
"ANTMINER T19": VNishT19,
"ANTMINER T21": VNishT21,
"ANTMINER S21": VNishS21,
"ANTMINER S21+": VNishS21Plus,
"ANTMINER S21+ HYD.": VNishS21PlusHydro,
"ANTMINER S21 PRO": VNishS21Pro,
"ANTMINER S21 HYD.": VNishS21Hydro,
},
MinerTypes.EPIC: {
None: ePIC,

View File

@@ -20,34 +20,36 @@ class ESPMinerWebAPI(BaseWebAPI):
**parameters: Any,
) -> dict:
url = f"http://{self.ip}:{self.port}/api/{command}"
try:
async with httpx.AsyncClient(
transport=settings.transport(),
) as client:
if parameters.get("post", False):
parameters.pop("post")
data = await client.post(
url,
timeout=settings.get("api_function_timeout", 3),
json=parameters,
)
elif parameters.get("patch", False):
parameters.pop("patch")
data = await client.patch(
url,
timeout=settings.get("api_function_timeout", 3),
json=parameters,
)
else:
data = await client.get(url)
except httpx.HTTPError:
pass
else:
if data.status_code == 200:
async with httpx.AsyncClient(transport=settings.transport()) as client:
for _ in range(settings.get("get_data_retries", 1)):
try:
return data.json()
except json.decoder.JSONDecodeError:
if parameters.get("post", False):
parameters.pop("post")
data = await client.post(
url,
timeout=settings.get("api_function_timeout", 3),
json=parameters,
)
elif parameters.get("patch", False):
parameters.pop("patch")
data = await client.patch(
url,
timeout=settings.get("api_function_timeout", 3),
json=parameters,
)
else:
data = await client.get(
url,
timeout=settings.get("api_function_timeout", 5),
)
except httpx.HTTPError:
pass
else:
if data.status_code == 200:
try:
return data.json()
except json.decoder.JSONDecodeError:
pass
async def multicommand(
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
@@ -94,3 +96,6 @@ class ESPMinerWebAPI(BaseWebAPI):
async def update_settings(self, **config):
return await self.send_command("system", patch=True, **config)
async def asic_info(self):
return await self.send_command("system/asic")

View File

@@ -58,7 +58,7 @@ class VNishWebAPI(BaseWebAPI):
allow_warning: bool = True,
privileged: bool = False,
**parameters: Any,
) -> dict:
) -> dict | None:
post = privileged or not parameters == {}
if self.token is None:
await self.auth()
@@ -126,6 +126,9 @@ class VNishWebAPI(BaseWebAPI):
async def summary(self) -> dict:
return await self.send_command("summary")
async def perf_summary(self) -> dict:
return await self.send_command("perf-summary")
async def chips(self) -> dict:
return await self.send_command("chips")

View File

@@ -1,6 +1,6 @@
[project]
name = "pyasic"
version = "0.72.8"
version = "0.72.11"
description = "A simplified and standardized interface for Bitcoin ASICs."
authors = [{name = "UpstreamData", email = "brett@upstreamdata.ca"}]