Compare commits

..

2 Commits

Author SHA1 Message Date
Brett Rowan
39410424b6 version: bump version number 2025-04-27 11:00:50 -06:00
Brett Rowan
7cb871b541 requirements: bump dependency versions 2025-04-27 11:00:34 -06:00
63 changed files with 209 additions and 1112 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. Use version 2.0+
- [pypoetry](https://python-poetry.org/): the reccommended way, since pyasic already uses it by default
```
poetry install

View File

@@ -157,19 +157,6 @@
show_root_heading: false
heading_level: 0
## S19j Pro+ (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jProPlus
handler: python
options:
show_root_heading: false
heading_level: 0
## S19j XP (Stock)
- [x] Shutdowns
@@ -183,19 +170,6 @@
show_root_heading: false
heading_level: 0
## S19j+ (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jPlus
handler: python
options:
show_root_heading: false
heading_level: 0
## S19j No PIC (Stock)
- [x] Shutdowns
@@ -313,19 +287,6 @@
show_root_heading: false
heading_level: 0
## S19 XP Hydro (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19XPHydro
handler: python
options:
show_root_heading: false
heading_level: 0
## S19+ (BOS+)
- [x] Shutdowns
@@ -482,19 +443,6 @@
show_root_heading: false
heading_level: 0
## S19 Hydro (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19Hydro
handler: python
options:
show_root_heading: false
heading_level: 0
## S19 Pro (VNish)
- [x] Shutdowns
@@ -508,19 +456,6 @@
show_root_heading: false
heading_level: 0
## S19 Pro A (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19ProA
handler: python
options:
show_root_heading: false
heading_level: 0
## S19 Pro Hydro (VNish)
- [x] Shutdowns
@@ -534,32 +469,6 @@
show_root_heading: false
heading_level: 0
## S19 XP (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19XP
handler: python
options:
show_root_heading: false
heading_level: 0
## S19 XP Hydro (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19XPHydro
handler: python
options:
show_root_heading: false
heading_level: 0
## S19a (VNish)
- [x] Shutdowns

View File

@@ -92,19 +92,6 @@
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
@@ -118,32 +105,6 @@
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
@@ -170,58 +131,6 @@
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

@@ -73,7 +73,7 @@
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X3.L3.VNishL3Plus
::: pyasic.miners.antminer.vnish.X3.L3.VnishL3Plus
handler: python
options:
show_root_heading: false

View File

@@ -47,7 +47,7 @@
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X7.L7.VNishL7
::: pyasic.miners.antminer.vnish.X7.L7.VnishL7
handler: python
options:
show_root_heading: false

View File

@@ -105,19 +105,6 @@
show_root_heading: false
heading_level: 0
## L9 (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X9.L9.VNishL9
handler: python
options:
show_root_heading: false
heading_level: 0
## T9 (Hive)
- [ ] Shutdowns

View File

@@ -1,19 +1,6 @@
# pyasic
## DGX Models
## DG1 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.elphapex.daoge.DGX.DG1.ElphapexDG1
handler: python
options:
show_root_heading: false
heading_level: 0
## DG1+ (Stock)
- [ ] Shutdowns
@@ -27,16 +14,3 @@
show_root_heading: false
heading_level: 0
## DG1Home (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.elphapex.daoge.DGX.DG1.ElphapexDG1Home
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -86,8 +86,6 @@ details {
<li><a href="../antminer/X19#s19j-no-pic-stock">S19j No PIC (Stock)</a></li>
<li><a href="../antminer/X19#s19-pro_1-stock">S19 Pro+ (Stock)</a></li>
<li><a href="../antminer/X19#s19j-pro-stock">S19j Pro (Stock)</a></li>
<li><a href="../antminer/X19#s19j_1-stock">S19j+ (Stock)</a></li>
<li><a href="../antminer/X19#s19j-pro_1-stock">S19j Pro+ (Stock)</a></li>
<li><a href="../antminer/X19#s19-xp-stock">S19 XP (Stock)</a></li>
<li><a href="../antminer/X19#s19a-stock">S19a (Stock)</a></li>
<li><a href="../antminer/X19#s19a-pro-stock">S19a Pro (Stock)</a></li>
@@ -565,12 +563,6 @@ details {
<li><a href="../avalonminer/A15X#avalon-1566-stock">Avalon 1566 (Stock)</a></li>
</ul>
</details>
<details>
<summary>Q Series:</summary>
<ul>
<li><a href="../avalonminer/Q#avalon-q-home-stock">Avalon Q Home (Stock)</a></li>
</ul>
</details>
</ul>
</details>
<details>
@@ -664,7 +656,6 @@ details {
<li><a href="../antminer/X19#s19-xp-bos_1">S19 XP (BOS+)</a></li>
<li><a href="../antminer/X19#s19-pro_1-hydro-bos_1">S19 Pro+ Hydro (BOS+)</a></li>
<li><a href="../antminer/X19#t19-bos_1">T19 (BOS+)</a></li>
<li><a href="../antminer/X19#s19-xp-hydro-bos_1">S19 XP Hydro (BOS+)</a></li>
</ul>
</details>
<details>
@@ -672,9 +663,6 @@ 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>
@@ -703,12 +691,6 @@ details {
<li><a href="../antminer/X7#l7-vnish">L7 (VNish)</a></li>
</ul>
</details>
<details>
<summary>X9 Series:</summary>
<ul>
<li><a href="../antminer/X9#l9-vnish">L9 (VNish)</a></li>
</ul>
</details>
<details>
<summary>X17 Series:</summary>
<ul>
@@ -724,15 +706,11 @@ details {
<li><a href="../antminer/X19#s19-pro-vnish">S19 Pro (VNish)</a></li>
<li><a href="../antminer/X19#s19j-vnish">S19j (VNish)</a></li>
<li><a href="../antminer/X19#s19i-vnish">S19i (VNish)</a></li>
<li><a href="../antminer/X19#s19-xp-vnish">S19 XP (VNish)</a></li>
<li><a href="../antminer/X19#s19-xp-hydro-vnish">S19 XP Hydro (VNish)</a></li>
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
<li><a href="../antminer/X19#s19a-vnish">S19a (VNish)</a></li>
<li><a href="../antminer/X19#s19-hydro-vnish">S19 Hydro (VNish)</a></li>
<li><a href="../antminer/X19#s19a-pro-vnish">S19a Pro (VNish)</a></li>
<li><a href="../antminer/X19#s19-pro-a-vnish">S19 Pro A (VNish)</a></li>
<li><a href="../antminer/X19#s19-pro-hydro-vnish">S19 Pro Hydro (VNish)</a></li>
<li><a href="../antminer/X19#s19k-pro-vnish">S19k Pro (VNish)</a></li>
<li><a href="../antminer/X19#t19-vnish">T19 (VNish)</a></li>
@@ -743,10 +721,6 @@ 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>
@@ -941,12 +915,6 @@ details {
<li><a href="../iceriver/KSX#ks5m-stock">KS5M (Stock)</a></li>
</ul>
</details>
<details>
<summary>ALX Series:</summary>
<ul>
<li><a href="../iceriver/ALX#al3-stock">AL3 (Stock)</a></li>
</ul>
</details>
</ul>
</details>
<details>
@@ -978,8 +946,6 @@ details {
<summary>DGX Series:</summary>
<ul>
<li><a href="../elphapex/DGX#dg1_1-stock">DG1+ (Stock)</a></li>
<li><a href="../elphapex/DGX#dg1-stock">DG1 (Stock)</a></li>
<li><a href="../elphapex/DGX#dg1home-stock">DG1Home (Stock)</a></li>
</ul>
</details>
</ul>

View File

@@ -283,17 +283,13 @@ class MinerConfig(BaseModel):
)
@classmethod
def from_vnish(
cls, web_settings: dict, web_presets: list[dict], web_perf_summary: dict
) -> "MinerConfig":
def from_vnish(cls, web_settings: dict, web_presets: list[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, web_perf_summary
),
mining_mode=MiningModeConfig.from_vnish(web_settings, web_presets),
)
@classmethod

View File

@@ -412,18 +412,12 @@ class MiningModePreset(MinerConfigValue):
@classmethod
def from_vnish(
cls,
web_overclock_settings: dict,
web_presets: list[dict],
web_perf_summary: dict,
cls, web_overclock_settings: dict, web_presets: list[dict]
) -> "MiningModePreset":
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
active_preset = 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],
@@ -709,9 +703,7 @@ class MiningModeConfig(MinerConfigOption):
return cls.default()
@classmethod
def from_vnish(
cls, web_settings: dict, web_presets: list[dict], web_perf_summary: dict
):
def from_vnish(cls, web_settings: dict, web_presets: list[dict]):
try:
mode_settings = web_settings["miner"]["overclock"]
except KeyError:
@@ -720,9 +712,7 @@ class MiningModeConfig(MinerConfigOption):
if mode_settings["preset"] == "disabled":
return MiningModeManual.from_vnish(mode_settings)
else:
return MiningModePreset.from_vnish(
mode_settings, web_presets, web_perf_summary
)
return MiningModePreset.from_vnish(mode_settings, web_presets)
@classmethod
def from_boser(cls, grpc_miner_conf: dict):

View File

@@ -70,7 +70,6 @@ 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.
"""
@@ -294,7 +293,7 @@ class MinerData(BaseModel):
@computed_field # type: ignore[misc]
@property
def efficiency_fract(self) -> float | None:
return self._efficiency(2)
self._efficiency(2)
def _efficiency(self, ndigits: int) -> float | None:
if self.hashrate is None or self.wattage is None:
@@ -302,7 +301,7 @@ class MinerData(BaseModel):
try:
return round(self.wattage / float(self.hashrate), ndigits)
except ZeroDivisionError:
return 0.0
return 0
@computed_field # type: ignore[misc]
@property

View File

@@ -1,6 +1,5 @@
from .base import MinerAlgoType
from .blake256 import Blake256Algo
from .blockflow import BlockFlowAlgo
from .eaglesong import EaglesongAlgo
from .equihash import EquihashAlgo
from .ethash import EtHashAlgo
@@ -25,4 +24,3 @@ class MinerAlgo:
EAGLESONG = EaglesongAlgo
ETHASH = EtHashAlgo
EQUIHASH = EquihashAlgo
BLOCKFLOW = BlockFlowAlgo

View File

@@ -1,12 +0,0 @@
from __future__ import annotations
from .base import MinerAlgoType
from .hashrate import BlockFlowHashRate
from .hashrate.unit import BlockFlowUnit
class BlockFlowAlgo(MinerAlgoType):
hashrate: type[BlockFlowHashRate] = BlockFlowHashRate
unit: type[BlockFlowUnit] = BlockFlowUnit
name = "BlockFlow"

View File

@@ -1,6 +1,5 @@
from .base import AlgoHashRateType
from .blake256 import Blake256HashRate
from .blockflow import BlockFlowHashRate
from .eaglesong import EaglesongHashRate
from .equihash import EquihashHashRate
from .ethash import EtHashHashRate
@@ -23,4 +22,3 @@ class AlgoHashRate:
EAGLESONG = EaglesongHashRate
ETHASH = EtHashHashRate
EQUIHASH = EquihashHashRate
BLOCKFLOW = BlockFlowHashRate

View File

@@ -1,18 +0,0 @@
from __future__ import annotations
from typing_extensions import Self
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm.hashrate.unit.blockflow import BlockFlowUnit
from .unit import HashUnit
class BlockFlowHashRate(AlgoHashRateType):
rate: float
unit: BlockFlowUnit = HashUnit.BLOCKFLOW.default
def into(self, other: BlockFlowUnit) -> Self:
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,5 +1,4 @@
from .blake256 import Blake256Unit
from .blockflow import BlockFlowUnit
from .eaglesong import EaglesongUnit
from .equihash import EquihashUnit
from .ethash import EtHashUnit
@@ -22,4 +21,3 @@ class HashUnit:
EAGLESONG = EaglesongUnit
ETHASH = EtHashUnit
EQUIHASH = EquihashUnit
BLOCKFLOW = BlockFlowUnit

View File

@@ -1,16 +0,0 @@
from __future__ import annotations
from .base import AlgoHashRateUnitType
class BlockFlowUnit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = MH

View File

@@ -43,7 +43,6 @@ class AntminerModels(MinerModelType):
S19jNoPIC = "S19j No PIC"
S19ProPlus = "S19 Pro+"
S19jPro = "S19j Pro"
S19jPlus = "S19j+"
S19jProNoPIC = "S19j Pro No PIC"
S19jProPlus = "S19j Pro+"
S19jProPlusNoPIC = "S19j Pro+ No PIC"
@@ -55,7 +54,6 @@ class AntminerModels(MinerModelType):
S19ProPlusHydro = "S19 Pro+ Hydro"
S19KPro = "S19K Pro"
S19kPro = "S19k Pro"
S19ProA = "S19 Pro A"
S19kProNoPIC = "S19k Pro No PIC"
S19jXP = "S19j XP"
T19 = "T19"
@@ -65,7 +63,6 @@ class AntminerModels(MinerModelType):
S21Pro = "S21 Pro"
S21Hydro = "S21 Hydro"
T21 = "T21"
S19XPHydro = "S19 XP Hydro"
def __str__(self):
return self.value
@@ -456,7 +453,6 @@ class AvalonminerModels(MinerModelType):
Avalon1566 = "Avalon 1566"
AvalonNano3 = "Avalon Nano 3"
AvalonNano3s = "Avalon Nano 3s"
AvalonQHome = "Avalon Q Home"
def __str__(self):
return self.value
@@ -536,7 +532,6 @@ class IceRiverModels(MinerModelType):
KS5 = "KS5"
KS5L = "KS5L"
KS5M = "KS5M"
AL3 = "AL3"
def __str__(self):
return self.value
@@ -562,9 +557,7 @@ class BraiinsModels(MinerModelType):
class ElphapexModels(MinerModelType):
DG1 = "DG1"
DG1Plus = "DG1+"
DG1Home = "DG1Home"
class MinerModel:

View File

@@ -25,9 +25,7 @@ from pyasic.miners.device.models import (
S19i,
S19j,
S19jNoPIC,
S19jPlus,
S19jPro,
S19jProPlus,
S19jXP,
S19KPro,
S19Plus,
@@ -82,10 +80,6 @@ class BMMinerS19jPro(AntminerModern, S19jPro):
pass
class BMMinerS19jPlus(AntminerModern, S19jPlus):
pass
class BMMinerS19L(AntminerModern, S19L):
pass
@@ -108,7 +102,3 @@ class BMMinerS19KPro(AntminerModern, S19KPro):
class BMMinerS19jXP(AntminerModern, S19jXP):
pass
class BMMinerS19jProPlus(AntminerModern, S19jProPlus):
pass

View File

@@ -22,9 +22,7 @@ from .S19 import (
BMMinerS19i,
BMMinerS19j,
BMMinerS19jNoPIC,
BMMinerS19jPlus,
BMMinerS19jPro,
BMMinerS19jProPlus,
BMMinerS19jXP,
BMMinerS19KPro,
BMMinerS19L,

View File

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

View File

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

View File

@@ -15,40 +15,12 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSer
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. -
# ------------------------------------------------------------------------------
from pyasic.miners.device.models import S21, S21Pro
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,11 +14,5 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .S21 import (
BOSMinerS21,
BOSMinerS21Hydro,
BOSMinerS21Plus,
BOSMinerS21PlusHydro,
BOSMinerS21Pro,
)
from .S21 import BOSMinerS21, BOSMinerS21Pro
from .T21 import BOSMinerT21

View File

@@ -20,16 +20,13 @@ from pyasic.miners.device.models import (
S19XP,
S19a,
S19aPro,
S19Hydro,
S19i,
S19j,
S19jPro,
S19kPro,
S19NoPIC,
S19Pro,
S19ProA,
S19ProHydro,
S19XPHydro,
)
@@ -45,18 +42,10 @@ class VNishS19Pro(VNish, S19Pro):
pass
class VNishS19Hydro(VNish, S19Hydro):
pass
class VNishS19XP(VNish, S19XP):
pass
class VNishS19XPHydro(VNish, S19XPHydro):
pass
class VNishS19a(VNish, S19a):
pass
@@ -65,10 +54,6 @@ class VNishS19aPro(VNish, S19aPro):
pass
class VNishS19ProA(VNish, S19ProA):
pass
class VNishS19i(VNish, S19i):
pass
@@ -87,7 +72,3 @@ class VNishS19ProHydro(VNish, S19ProHydro):
class VNishS19kPro(VNish, S19kPro):
pass
class VNishS19ProA(VNish, S19ProA):
pass

View File

@@ -18,16 +18,13 @@ from .S19 import (
VNishS19,
VNishS19a,
VNishS19aPro,
VNishS19Hydro,
VNishS19i,
VNishS19j,
VNishS19jPro,
VNishS19kPro,
VNishS19NoPIC,
VNishS19Pro,
VNishS19ProA,
VNishS19ProHydro,
VNishS19XP,
VNishS19XPHydro,
)
from .T19 import VNishT19

View File

@@ -15,24 +15,8 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import VNish
from pyasic.miners.device.models import S21, S21Hydro, S21Plus, S21PlusHydro, S21Pro
from pyasic.miners.device.models import S21
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,11 +14,5 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .S21 import (
VNishS21,
VNishS21Hydro,
VNishS21Plus,
VNishS21PlusHydro,
VNishS21Pro,
)
from .S21 import VNishS21
from .T21 import VNishT21

View File

@@ -18,5 +18,5 @@ from pyasic.miners.backends import VNish
from pyasic.miners.device.models import L3Plus
class VNishL3Plus(VNish, L3Plus):
class VnishL3Plus(VNish, L3Plus):
pass

View File

@@ -14,4 +14,4 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .L3 import VNishL3Plus
from .L3 import VnishL3Plus

View File

@@ -18,5 +18,5 @@ from pyasic.miners.backends import VNish
from pyasic.miners.device.models import L7
class VNishL7(VNish, L7):
class VnishL7(VNish, L7):
pass

View File

@@ -14,4 +14,4 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .L7 import VNishL7
from .L7 import VnishL7

View File

@@ -1,22 +0,0 @@
# ------------------------------------------------------------------------------
# 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 VNish
from pyasic.miners.device.models import L9
class VNishL9(VNish, L9):
pass

View File

@@ -1,17 +0,0 @@
# ------------------------------------------------------------------------------
# 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 .L9 import VNishL9

View File

@@ -16,7 +16,6 @@
from .X3 import *
from .X7 import *
from .X9 import *
from .X17 import *
from .X19 import *
from .X21 import *

View File

@@ -1,22 +0,0 @@
# ------------------------------------------------------------------------------
# Copyright 2025 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 AvalonQHome
class CGMinerAvalonQHome(AvalonMiner, AvalonQHome):
pass

View File

@@ -1 +0,0 @@
from .Q import CGMinerAvalonQHome

View File

@@ -22,4 +22,3 @@ from .A11X import *
from .A12X import *
from .A15X import *
from .nano import *
from .Q import *

View File

@@ -49,31 +49,31 @@ AVALON_NANO_DATA_LOC = DataLocations(
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"_get_env_temp",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
@@ -102,35 +102,35 @@ AVALON_NANO3S_DATA_LOC = DataLocations(
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"_get_env_temp",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
@@ -170,58 +170,58 @@ class CGMinerAvalonNano3s(AvalonMiner, AvalonNano3s):
data_locations = AVALON_NANO3S_DATA_LOC
async def _get_wattage(self, rpc_estats: dict = None) -> Optional[int]:
if rpc_estats is None:
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
if rpc_stats is None:
try:
rpc_estats = await self.rpc.estats()
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_estats is not None:
if rpc_stats is not None:
try:
unparsed_estats = rpc_estats["STATS"][0]["MM ID0"]
parsed_estats = self.parse_estats(unparsed_estats)
return int(parsed_estats["PS"][6])
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
return int(parsed_stats["PS"][6])
except (IndexError, KeyError, ValueError, TypeError):
pass
async def _get_hashrate(self, rpc_estats: dict = None) -> Optional[AlgoHashRate]:
if rpc_estats is None:
async def _get_hashrate(self, rpc_stats: dict = None) -> Optional[AlgoHashRate]:
if rpc_stats is None:
try:
rpc_estats = await self.rpc.estats()
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_estats is not None:
if rpc_stats is not None:
try:
unparsed_estats = rpc_estats["STATS"][0]["MM ID0"]
parsed_estats = self.parse_estats(unparsed_estats)
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
return self.algo.hashrate(
rate=float(parsed_estats["GHSspd"]), unit=self.algo.unit.GH
rate=float(parsed_stats["GHSspd"][0]), unit=self.algo.unit.GH
).into(self.algo.unit.default)
except (IndexError, KeyError, ValueError, TypeError):
pass
async def _get_hashboards(self, rpc_estats: dict = None) -> List[HashBoard]:
hashboards = await AvalonMiner._get_hashboards(self, rpc_estats)
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
hashboards = await AvalonMiner._get_hashboards(self, rpc_stats)
if rpc_estats is None:
if rpc_stats is None:
try:
rpc_estats = await self.rpc.estats()
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_estats is not None:
if rpc_stats is not None:
try:
unparsed_estats = rpc_estats["STATS"][0]["MM ID0"]
parsed_estats = self.parse_estats(unparsed_estats)
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
except (IndexError, KeyError, ValueError, TypeError):
return hashboards
for board in range(len(hashboards)):
try:
board_hr = parsed_estats["GHSspd"][board]
board_hr = parsed_stats["GHSspd"][board]
hashboards[board].hashrate = self.algo.hashrate(
rate=float(board_hr), unit=self.algo.unit.GH
).into(self.algo.unit.default)

View File

@@ -13,9 +13,8 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
import copy
import re
import time
from typing import List, Optional
from pyasic.data import Fan, HashBoard
@@ -23,7 +22,6 @@ from pyasic.device.algorithm import AlgoHashRate
from pyasic.errors import APIError
from pyasic.miners.backends.cgminer import CGMiner
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.rpc.avalonminer import AvalonMinerRPCAPI
AVALON_DATA_LOC = DataLocations(
**{
@@ -45,31 +43,31 @@ AVALON_DATA_LOC = DataLocations(
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"_get_env_temp",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light",
[RPCAPICommand("rpc_estats", "estats")],
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
@@ -86,9 +84,6 @@ AVALON_DATA_LOC = DataLocations(
class AvalonMiner(CGMiner):
"""Handler for Avalon Miners"""
_rpc_cls = AvalonMinerRPCAPI
rpc: AvalonMinerRPCAPI
data_locations = AVALON_DATA_LOC
async def fault_light_on(self) -> bool:
@@ -139,94 +134,45 @@ class AvalonMiner(CGMiner):
return False
return False
async def stop_mining(self) -> bool:
try:
# Shut off 5 seconds from now
timestamp = int(time.time()) + 5
data = await self.rpc.ascset(0, f"softoff", f"1:{timestamp}")
except APIError:
return False
if "success" in data["STATUS"][0]["Msg"]:
return True
return False
async def resume_mining(self) -> bool:
try:
# Shut off 5 seconds from now
timestamp = int(time.time()) + 5
data = await self.rpc.ascset(0, f"softon", f"1:{timestamp}")
except APIError:
return False
if "success" in data["STATUS"][0]["Msg"]:
return True
return False
@staticmethod
def parse_estats(data):
# Deep copy to preserve original structure
new_data = copy.deepcopy(data)
def convert_value(val, key):
val = val.strip()
if key == "SYSTEMSTATU":
return val
if " " in val:
parts = val.split()
result = []
for part in parts:
if part.isdigit():
result.append(int(part))
else:
try:
result.append(float(part))
except ValueError:
result.append(part)
return result
else:
if val.isdigit():
return int(val)
def parse_stats(stats):
_stats_items = re.findall(".+?\\[*?]", stats)
stats_items = []
stats_dict = {}
for item in _stats_items:
if ": " in item:
data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {}
try:
return float(val)
for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
except ValueError:
return val
# --avalon args
for arg_item in data_list:
item_data = arg_item[0].split(" ")
for idx, val in enumerate(item_data):
if idx % 2 == 0 or idx == 0:
data_dict[val] = item_data[idx + 1]
def parse_info_block(info_str):
pattern = re.compile(r"(\w+)\[([^\]]*)\]")
return {
key: convert_value(val, key) for key, val in pattern.findall(info_str)
}
raw_data = [data[0].strip(), data_dict]
else:
raw_data = [
value
for value in item.replace("[", " ")
.replace("]", " ")
.split(" ")[:-1]
if value != ""
]
if len(raw_data) == 1:
raw_data.append("")
if raw_data[0] == "":
raw_data = raw_data[1:]
for stat in new_data.get("STATS", []):
keys_to_replace = {}
stats_dict[raw_data[0]] = raw_data[1:]
stats_items.append(raw_data)
for key, value in stat.items():
if "MM" in key:
# Normalize key by removing suffix after colon
norm_key = key.split(":")[0]
mm_data = value
if not isinstance(mm_data, str):
continue
if mm_data.startswith("'STATS':"):
mm_data = mm_data[len("'STATS':") :]
keys_to_replace[norm_key] = parse_info_block(mm_data)
elif key == "HBinfo":
match = re.search(r"'(\w+)':\{(.+)\}", value)
if match:
hb_key = match.group(1)
hb_data = match.group(2)
keys_to_replace[key] = {hb_key: parse_info_block(hb_data)}
# Remove old keys and insert parsed versions
for k in list(stat.keys()):
if "MM" in k or k == "HBinfo":
del stat[k]
stat.update(keys_to_replace)
return new_data
return stats_dict
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
@@ -265,7 +211,7 @@ class AvalonMiner(CGMiner):
except (KeyError, IndexError, ValueError, TypeError):
pass
async def _get_hashboards(self, rpc_estats: dict = None) -> List[HashBoard]:
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
if self.expected_hashboards is None:
return []
@@ -274,202 +220,164 @@ class AvalonMiner(CGMiner):
for i in range(self.expected_hashboards)
]
if rpc_estats is None:
if rpc_stats is None:
try:
rpc_estats = await self.rpc.estats()
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_estats is not None:
if rpc_stats is not None:
try:
parsed_estats = self.parse_estats(rpc_estats)
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
except (IndexError, KeyError, ValueError, TypeError):
return hashboards
for board in range(self.expected_hashboards):
try:
board_hr = parsed_estats["STATS"][0]["MM ID0"]["MGHS"]
if isinstance(board_hr, list):
hashboards[board].hashrate = self.algo.hashrate(
rate=float(board_hr[board]), unit=self.algo.unit.GH
).into(self.algo.unit.default)
else:
hashboards[board].hashrate = self.algo.hashrate(
rate=float(board_hr), unit=self.algo.unit.GH
).into(self.algo.unit.default)
hashboards[board].chip_temp = int(parsed_stats["MTmax"][board])
except LookupError:
pass
try:
hashboards[board].chip_temp = int(
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
)
board_hr = parsed_stats["MGHS"][board]
hashboards[board].hashrate = self.algo.hashrate(
rate=float(board_hr), unit=self.algo.unit.GH
).into(self.algo.unit.default)
except LookupError:
try:
hashboards[board].chip_temp = int(
parsed_estats["STATS"][0]["MM ID0"]["Tmax"]
)
except LookupError:
pass
pass
try:
hashboards[board].temp = int(
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
)
hashboards[board].temp = int(parsed_stats["MTavg"][board])
except LookupError:
try:
hashboards[board].temp = int(
parsed_estats["STATS"][0]["MM ID0"]["Tavg"]
)
except LookupError:
pass
pass
try:
hashboards[board].inlet_temp = int(
parsed_estats["STATS"][0]["MM ID0"]["MTavg"][board]
)
except LookupError:
try:
hashboards[board].inlet_temp = int(
parsed_estats["STATS"][0]["MM ID0"]["HBITemp"]
)
except LookupError:
pass
try:
hashboards[board].outlet_temp = int(
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
)
except LookupError:
try:
hashboards[board].outlet_temp = int(
parsed_estats["STATS"][0]["MM ID0"]["HBOTemp"]
)
except LookupError:
pass
try:
chip_data = parsed_estats["STATS"][0]["MM ID0"][f"PVT_T{board}"]
chip_data = parsed_stats[f"PVT_T{board}"]
hashboards[board].missing = False
if chip_data:
hashboards[board].chips = len(
[item for item in chip_data if not item == "0"]
)
except LookupError:
try:
chip_data = parsed_estats["STATS"][0]["HBinfo"][f"HB{board}"][
f"PVT_T{board}"
]
hashboards[board].missing = False
if chip_data:
hashboards[board].chips = len(
[item for item in chip_data if not item == "0"]
)
except LookupError:
pass
pass
return hashboards
async def _get_expected_hashrate(
self, rpc_estats: dict = None
self, rpc_stats: dict = None
) -> Optional[AlgoHashRate]:
if rpc_estats is None:
if rpc_stats is None:
try:
rpc_estats = await self.rpc.estats()
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_estats is not None:
if rpc_stats is not None:
try:
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
return self.algo.hashrate(
rate=float(parsed_estats["GHSmm"]), unit=self.algo.unit.GH
rate=float(parsed_stats["GHSmm"][0]), unit=self.algo.unit.GH
).into(self.algo.unit.default)
except (IndexError, KeyError, ValueError, TypeError):
pass
async def _get_env_temp(self, rpc_estats: dict = None) -> Optional[float]:
if rpc_estats is None:
async def _get_env_temp(self, rpc_stats: dict = None) -> Optional[float]:
if rpc_stats is None:
try:
rpc_estats = await self.rpc.estats()
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_estats is not None:
if rpc_stats is not None:
try:
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
return float(parsed_estats["Temp"])
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
return float(parsed_stats["Temp"][0])
except (IndexError, KeyError, ValueError, TypeError):
pass
async def _get_wattage_limit(self, rpc_estats: dict = None) -> Optional[int]:
if rpc_estats is None:
async def _get_wattage_limit(self, rpc_stats: dict = None) -> Optional[int]:
if rpc_stats is None:
try:
rpc_estats = await self.rpc.estats()
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_estats is not None:
if rpc_stats is not None:
try:
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
return int(parsed_estats["MPO"])
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
return int(parsed_stats["MPO"][0])
except (IndexError, KeyError, ValueError, TypeError):
pass
async def _get_wattage(self, rpc_estats: dict = None) -> Optional[int]:
if rpc_estats is None:
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
if rpc_stats is None:
try:
rpc_estats = await self.rpc.estats()
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_estats is not None:
if rpc_stats is not None:
try:
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
return int(parsed_estats["WALLPOWER"])
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
return int(parsed_stats["WALLPOWER"][0])
except (IndexError, KeyError, ValueError, TypeError):
pass
async def _get_fans(self, rpc_estats: dict = None) -> List[Fan]:
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
if self.expected_fans is None:
return []
if rpc_estats is None:
if rpc_stats is None:
try:
rpc_estats = await self.rpc.estats()
rpc_stats = await self.rpc.stats()
except APIError:
pass
fans_data = [Fan() for _ in range(self.expected_fans)]
if rpc_estats is not None:
if rpc_stats is not None:
try:
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
except LookupError:
return fans_data
for fan in range(self.expected_fans):
try:
fans_data[fan].speed = int(parsed_estats[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
async def _get_fault_light(self, rpc_estats: dict = None) -> Optional[bool]:
async def _get_fault_light(self, rpc_stats: dict = None) -> Optional[bool]:
if self.light:
return self.light
if rpc_estats is None:
if rpc_stats is None:
try:
rpc_estats = await self.rpc.estats()
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_estats is not None:
if rpc_stats is not None:
try:
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
led = int(parsed_estats["Led"])
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
led = int(parsed_stats["Led"][0])
return True if led == 1 else False
except (IndexError, KeyError, ValueError, TypeError):
pass
try:
data = await self.rpc.ascset(0, "led", "1-255")
except APIError:
return False
try:
if data["STATUS"][0]["Msg"] == "ASC 0 set info: LED[1]":
return True
except LookupError:
pass
return False

View File

@@ -115,18 +115,11 @@ class ESPMiner(BaseMiner):
if web_system_info is not None:
try:
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
expected_hashrate = (
web_system_info.get("smallCoreCount")
* web_system_info.get("asicCount")
* web_system_info.get("frequency")
)
return self.algo.hashrate(
rate=float(expected_hashrate), unit=self.algo.unit.MH

View File

@@ -293,12 +293,9 @@ 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, web_perf_summary
)
self.config = MinerConfig.from_vnish(web_settings, web_presets)
return self.config
async def set_power_limit(self, wattage: int) -> bool:

View File

@@ -126,15 +126,6 @@ class S19jPro(AntMinerMake):
algo = MinerAlgo.SHA256
class S19jPlus(AntMinerMake):
raw_model = MinerModel.ANTMINER.S19jPlus
expected_chips = 108
expected_fans = 4
expected_hashboards = 3
algo = MinerAlgo.SHA256
class S19jProNoPIC(AntMinerMake):
raw_model = MinerModel.ANTMINER.S19jProNoPIC
@@ -225,15 +216,6 @@ class S19KPro(AntMinerMake):
algo = MinerAlgo.SHA256
class S19ProA(AntMinerMake):
raw_model = MinerModel.ANTMINER.S19ProA
expected_chips = 114
expected_fans = 4
expected_hashboards = 3
algo = MinerAlgo.SHA256
class S19jXP(AntMinerMake):
raw_model = MinerModel.ANTMINER.S19jXP
@@ -241,12 +223,3 @@ 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

@@ -24,7 +24,6 @@ from .S19 import (
S19i,
S19j,
S19jNoPIC,
S19jPlus,
S19jPro,
S19jProNoPIC,
S19jProPlus,
@@ -36,10 +35,8 @@ from .S19 import (
S19NoPIC,
S19Plus,
S19Pro,
S19ProA,
S19ProHydro,
S19ProPlus,
S19ProPlusHydro,
S19XPHydro,
)
from .T19 import T19

View File

@@ -1,27 +0,0 @@
# ------------------------------------------------------------------------------
# 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.device.algorithm import MinerAlgo
from pyasic.device.models import MinerModel
from pyasic.miners.device.makes import AvalonMinerMake
class AvalonQHome(AvalonMinerMake):
raw_model = MinerModel.AVALONMINER.AvalonQHome
expected_chips = 160
expected_fans = 2
expected_hashboards = 1
algo = MinerAlgo.SHA256

View File

@@ -1,17 +0,0 @@
# ------------------------------------------------------------------------------
# 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 .Q import AvalonQHome

View File

@@ -22,4 +22,3 @@ from .A11X import *
from .A12X import *
from .A15X import *
from .nano import *
from .Q import *

View File

@@ -6,7 +6,6 @@ from pyasic.miners.device.makes import BraiinsMake
class BMM100(BraiinsMake):
raw_model = MinerModel.BRAIINS.BMM100
expected_chips = 1
expected_hashboards = 1
expected_fans = 1
algo = MinerAlgo.SHA256
@@ -15,7 +14,6 @@ class BMM100(BraiinsMake):
class BMM101(BraiinsMake):
raw_model = MinerModel.BRAIINS.BMM101
expected_chips = 1
expected_hashboards = 1
expected_fans = 1
algo = MinerAlgo.SHA256

View File

@@ -18,15 +18,6 @@ from pyasic.device.models import MinerModel
from pyasic.miners.device.makes import ElphapexMake
class DG1(ElphapexMake):
raw_model = MinerModel.ELPHAPEX.DG1
expected_chips = 144
expected_hashboards = 4
expected_fans = 4
algo = MinerAlgo.SCRYPT
class DG1Plus(ElphapexMake):
raw_model = MinerModel.ELPHAPEX.DG1Plus
@@ -34,12 +25,3 @@ class DG1Plus(ElphapexMake):
expected_hashboards = 4
expected_fans = 4
algo = MinerAlgo.SCRYPT
class DG1Home(ElphapexMake):
raw_model = MinerModel.ELPHAPEX.DG1Home
expected_chips = 120
expected_hashboards = 4
expected_fans = 4
algo = MinerAlgo.SCRYPT

View File

@@ -1 +1 @@
from .DG1 import DG1, DG1Home, DG1Plus
from .DG1 import DG1Plus

View File

@@ -1,27 +0,0 @@
# ------------------------------------------------------------------------------
# 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.algorithm import MinerAlgo
from pyasic.device.models import MinerModel
from pyasic.miners.device.makes import IceRiverMake
class AL3(IceRiverMake):
raw_model = MinerModel.ICERIVER.AL3
expected_chips = 156
expected_fans = 4
expected_hashboards = 3
algo = MinerAlgo.BLOCKFLOW

View File

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

View File

@@ -1,2 +1 @@
from .ALX import *
from .KSX import *

View File

@@ -1,14 +1,6 @@
from pyasic.miners.backends.elphapex import ElphapexMiner
from pyasic.miners.device.models import DG1, DG1Home, DG1Plus
from pyasic.miners.device.models import DG1Plus
class ElphapexDG1Plus(ElphapexMiner, DG1Plus):
pass
class ElphapexDG1(ElphapexMiner, DG1):
pass
class ElphapexDG1Home(ElphapexMiner, DG1Home):
pass

View File

@@ -1 +1 @@
from .DG1 import ElphapexDG1, ElphapexDG1Home, ElphapexDG1Plus
from .DG1 import ElphapexDG1Plus

View File

@@ -41,7 +41,6 @@ from pyasic.miners.elphapex import *
from pyasic.miners.goldshell import *
from pyasic.miners.hammer import *
from pyasic.miners.iceriver import *
from pyasic.miners.iceriver.iceminer.ALX import IceRiverAL3
from pyasic.miners.innosilicon import *
from pyasic.miners.luckyminer import *
from pyasic.miners.volcminer import *
@@ -109,8 +108,6 @@ MINER_CLASSES = {
"ANTMINER S19J88NOPIC": BMMinerS19jNoPIC,
"ANTMINER S19PRO+": BMMinerS19ProPlus,
"ANTMINER S19J PRO": BMMinerS19jPro,
"ANTMINER S19J+": BMMinerS19jPlus,
"ANTMINER S19J PRO+": BMMinerS19jProPlus,
"ANTMINER S19 XP": BMMinerS19XP,
"ANTMINER S19A": BMMinerS19a,
"ANTMINER S19A PRO": BMMinerS19aPro,
@@ -512,7 +509,6 @@ MINER_CLASSES = {
"AVALONMINER NANO3": CGMinerAvalonNano3,
"AVALON NANO3S": CGMinerAvalonNano3s,
"AVALONMINER 15-194": CGMinerAvalon1566,
"AVALON Q": CGMinerAvalonQHome,
},
MinerTypes.INNOSILICON: {
None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}),
@@ -559,20 +555,15 @@ 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,
"L3+": VNishL3Plus,
"ANTMINER L3+": VNishL3Plus,
"ANTMINER L7": VNishL7,
"ANTMINER L9": VNishL9,
"L3+": VnishL3Plus,
"ANTMINER L3+": VnishL3Plus,
"ANTMINER L7": VnishL7,
"ANTMINER S17+": VNishS17Plus,
"ANTMINER S17 PRO": VNishS17Pro,
"ANTMINER S19": VNishS19,
@@ -580,24 +571,16 @@ MINER_CLASSES = {
"ANTMINER S19 PRO": VNishS19Pro,
"ANTMINER S19J": VNishS19j,
"ANTMINER S19I": VNishS19i,
"ANTMINER S19 XP": VNishS19XP,
"ANTMINER S19 XP HYD.": VNishS19XPHydro,
"ANTMINER S19J PRO": VNishS19jPro,
"ANTMINER S19J PRO A": VNishS19jPro,
"ANTMINER S19J PRO BB": VNishS19jPro,
"ANTMINER S19A": VNishS19a,
"ANTMINER S19 HYD.": VNishS19Hydro,
"ANTMINER S19A PRO": VNishS19aPro,
"ANTMINER S19 PRO A": VNishS19ProA,
"ANTMINER S19 PRO HYD.": VNishS19ProHydro,
"ANTMINER S19K PRO": VNishS19kPro,
"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,
@@ -687,7 +670,6 @@ MINER_CLASSES = {
"KS5": IceRiverKS5,
"KS5L": IceRiverKS5L,
"KS5M": IceRiverKS5M,
"10306": IceRiverAL3,
},
MinerTypes.HAMMER: {
None: type("HammerUnknown", (BlackMiner, HammerMake), {}),
@@ -700,8 +682,6 @@ MINER_CLASSES = {
MinerTypes.ELPHAPEX: {
None: type("ElphapexUnknown", (ElphapexMiner, ElphapexMake), {}),
"DG1+": ElphapexDG1Plus,
"DG1": ElphapexDG1,
"DG1-Home": ElphapexDG1Home,
},
}

View File

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

View File

@@ -1 +0,0 @@
from .AL3 import IceRiverAL3

View File

@@ -1,27 +0,0 @@
# ------------------------------------------------------------------------------
# Copyright 2025 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.rpc.cgminer import CGMinerRPCAPI
class AvalonMinerRPCAPI(CGMinerRPCAPI):
"""An abstraction of the AvalonMiner API.
Each method corresponds to an API command in AvalonMiner.
"""
async def litestats(self):
return await self.send_command("litestats")

View File

@@ -20,36 +20,34 @@ class ESPMinerWebAPI(BaseWebAPI):
**parameters: Any,
) -> dict:
url = f"http://{self.ip}:{self.port}/api/{command}"
async with httpx.AsyncClient(transport=settings.transport()) as client:
for _ in range(settings.get("get_data_retries", 1)):
try:
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
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:
if data.status_code == 200:
try:
return data.json()
except json.decoder.JSONDecodeError:
pass
data = await client.get(url)
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
@@ -96,6 +94,3 @@ 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 | None:
) -> dict:
post = privileged or not parameters == {}
if self.token is None:
await self.auth()
@@ -126,9 +126,6 @@ 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.74.1"
version = "0.72.8"
description = "A simplified and standardized interface for Bitcoin ASICs."
authors = [{name = "UpstreamData", email = "brett@upstreamdata.ca"}]

File diff suppressed because one or more lines are too long