Compare commits

...

9 Commits

Author SHA1 Message Date
Brett Rowan
a62bea33a7 docs: update docs 2025-04-18 16:15:31 -06:00
pre-commit-ci[bot]
406bcd179c [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-04-18 16:12:55 -06:00
Ryan Heideman
aa87ef7d71 nano3s fixes 2025-04-18 16:12:55 -06:00
SKART1
ec88fbf6aa Fix #334 efficiency calculation and add computed fields 2025-04-18 16:12:44 -06:00
pre-commit-ci[bot]
e23c86a944 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/python-poetry/poetry: 2.1.1 → 2.1.2](https://github.com/python-poetry/poetry/compare/2.1.1...2.1.2)
2025-04-16 08:41:30 -06:00
Brett Rowan
b81276cb19 version: bump version number 2025-04-16 08:40:36 -06:00
Adrian
8dcc72b1bb T21 working on LuxOS (#331) 2025-04-14 12:55:54 -06:00
Timur Taipov
540572356f feature: add support for S21+ hydro (#326) 2025-04-14 09:15:06 -06:00
Ryan Heideman
83035a869b feature: Add Support for Avalon Nano 3s (#329) 2025-04-12 15:34:45 -06:00
22 changed files with 298 additions and 31 deletions

View File

@@ -5,7 +5,7 @@ ci:
- generate-docs
repos:
- repo: https://github.com/python-poetry/poetry
rev: 2.1.1
rev: 2.1.2
hooks:
- id: poetry-check
- id: poetry-lock

View File

@@ -759,7 +759,7 @@
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19
@@ -772,7 +772,7 @@
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19Pro
@@ -785,7 +785,7 @@
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19XP
@@ -798,7 +798,7 @@
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19jPro
@@ -811,7 +811,7 @@
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19jProPlus
@@ -824,7 +824,7 @@
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19kPro
@@ -837,7 +837,7 @@
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X19.T19.LUXMinerT19

View File

@@ -53,6 +53,19 @@
show_root_heading: false
heading_level: 0
## S21+ Hydro (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21PlusHydro
handler: python
options:
show_root_heading: false
heading_level: 0
## T21 (Stock)
- [x] Shutdowns
@@ -174,7 +187,7 @@
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X21.S21.LUXMinerS21
@@ -183,6 +196,19 @@
show_root_heading: false
heading_level: 0
## T21 (LuxOS)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X21.T21.LUXMinerT21
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 (MaraFW)
- [ ] Shutdowns

View File

@@ -122,7 +122,7 @@
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X9.S9.LUXMinerS9

View File

@@ -1,6 +1,19 @@
# pyasic
## nano Models
## Avalon Nano 3s (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.avalonminer.cgminer.nano.nano3.CGMinerAvalonNano3s
handler: python
options:
show_root_heading: false
heading_level: 0
## Avalon Nano 3 (Stock)
- [ ] Shutdowns

View File

@@ -104,6 +104,7 @@ details {
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
<li><a href="../antminer/X21#s21_1-stock">S21+ (Stock)</a></li>
<li><a href="../antminer/X21#s21_1-hydro-stock">S21+ Hydro (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>
<li><a href="../antminer/X21#s21-hydro-stock">S21 Hydro (Stock)</a></li>
@@ -553,6 +554,7 @@ details {
<summary>nano Series:</summary>
<ul>
<li><a href="../avalonminer/nano#avalon-nano-3-stock">Avalon Nano 3 (Stock)</a></li>
<li><a href="../avalonminer/nano#avalon-nano-3s-stock">Avalon Nano 3s (Stock)</a></li>
</ul>
</details>
<details>
@@ -814,6 +816,7 @@ details {
<summary>X21 Series:</summary>
<ul>
<li><a href="../antminer/X21#s21-luxos">S21 (LuxOS)</a></li>
<li><a href="../antminer/X21#t21-luxos">T21 (LuxOS)</a></li>
</ul>
</details>
</ul>

View File

@@ -284,10 +284,22 @@ class MinerData(BaseModel):
@computed_field # type: ignore[misc]
@property
def efficiency(self) -> int | None:
efficiency = self._efficiency(0)
if efficiency is None:
return None
else:
return int(efficiency)
@computed_field # type: ignore[misc]
@property
def efficiency_fract(self) -> float | None:
self._efficiency(2)
def _efficiency(self, ndigits: int) -> float | None:
if self.hashrate is None or self.wattage is None:
return None
try:
return round(self.wattage / float(self.hashrate))
return round(self.wattage / float(self.hashrate), ndigits)
except ZeroDivisionError:
return 0

View File

@@ -28,6 +28,8 @@ class HashBoard(BaseModel):
Attributes:
slot: The slot of the board as an int.
hashrate: The hashrate of the board in TH/s as a float.
inlet_temp: Inlet temperature for hydro asics as an int
outlet_temp: Outlet temperature for hydro asics as an int
temp: The temperature of the PCB as an int.
chip_temp: The temperature of the chips as an int.
chips: The chip count of the board as an int.
@@ -41,6 +43,8 @@ class HashBoard(BaseModel):
slot: int = 0
hashrate: AlgoHashRateType | None = None
inlet_temp: float | None = None
outlet_temp: float | None = None
temp: float | None = None
chip_temp: float | None = None
chips: int | None = None

View File

@@ -59,6 +59,7 @@ class AntminerModels(MinerModelType):
T19 = "T19"
S21 = "S21"
S21Plus = "S21+"
S21PlusHydro = "S21+ Hydro"
S21Pro = "S21 Pro"
S21Hydro = "S21 Hydro"
T21 = "T21"
@@ -451,6 +452,7 @@ class AvalonminerModels(MinerModelType):
Avalon1246 = "Avalon 1246"
Avalon1566 = "Avalon 1566"
AvalonNano3 = "Avalon Nano 3"
AvalonNano3s = "Avalon Nano 3s"
def __str__(self):
return self.value

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerModern
from pyasic.miners.device.models import S21, S21Hydro, S21Plus, S21Pro
from pyasic.miners.device.models import S21, S21Hydro, S21Plus, S21PlusHydro, S21Pro
class BMMinerS21(AntminerModern, S21):
@@ -26,6 +26,10 @@ class BMMinerS21Plus(AntminerModern, S21Plus):
pass
class BMMinerS21PlusHydro(AntminerModern, S21PlusHydro):
pass
class BMMinerS21Pro(AntminerModern, S21Pro):
pass

View File

@@ -13,5 +13,11 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from .S21 import BMMinerS21, BMMinerS21Hydro, BMMinerS21Plus, BMMinerS21Pro
from .S21 import (
BMMinerS21,
BMMinerS21Hydro,
BMMinerS21Plus,
BMMinerS21PlusHydro,
BMMinerS21Pro,
)
from .T21 import BMMinerT21

View File

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

View File

@@ -15,3 +15,4 @@
# ------------------------------------------------------------------------------
from .S21 import LUXMinerS21
from .T21 import LUXMinerT21

View File

@@ -14,4 +14,4 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .nano3 import CGMinerAvalonNano3
from .nano3 import CGMinerAvalonNano3, CGMinerAvalonNano3s

View File

@@ -13,9 +13,11 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from typing import Optional
from typing import List, Optional
from pyasic import APIError
from pyasic.data.boards import HashBoard
from pyasic.device.algorithm.hashrate import AlgoHashRate
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.data import (
DataFunction,
@@ -24,7 +26,7 @@ from pyasic.miners.data import (
RPCAPICommand,
WebAPICommand,
)
from pyasic.miners.device.models import AvalonNano3
from pyasic.miners.device.models import AvalonNano3, AvalonNano3s
from pyasic.web.avalonminer import AvalonMinerWebAPI
AVALON_NANO_DATA_LOC = DataLocations(
@@ -84,6 +86,63 @@ AVALON_NANO_DATA_LOC = DataLocations(
}
)
AVALON_NANO3S_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"_get_mac",
[RPCAPICommand("rpc_version", "version")],
),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver",
[RPCAPICommand("rpc_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver",
[RPCAPICommand("rpc_version", "version")],
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"_get_env_temp",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_pools", "pools")],
),
}
)
class CGMinerAvalonNano3(AvalonMiner, AvalonNano3):
_web_cls = AvalonMinerWebAPI
@@ -105,3 +164,68 @@ class CGMinerAvalonNano3(AvalonMiner, AvalonNano3):
return mac.upper()
except (KeyError, ValueError):
pass
class CGMinerAvalonNano3s(AvalonMiner, AvalonNano3s):
data_locations = AVALON_NANO3S_DATA_LOC
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_stats is not None:
try:
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_stats: dict = None) -> Optional[AlgoHashRate]:
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_stats is not None:
try:
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
return self.algo.hashrate(
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_stats: dict = None) -> List[HashBoard]:
hashboards = await AvalonMiner._get_hashboards(self, rpc_stats)
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_stats is not None:
try:
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_stats["GHSspd"][board]
hashboards[board].hashrate = self.algo.hashrate(
rate=float(board_hr), unit=self.algo.unit.GH
).into(self.algo.unit.default)
except LookupError:
pass
return hashboards

View File

@@ -272,18 +272,47 @@ class AntminerModern(BMMiner):
rate=board["rate_real"], unit=self.algo.unit.GH
).into(self.algo.unit.default)
hashboards[board["index"]].chips = board["asic_num"]
board_temp_data = list(
filter(lambda x: not x == 0, board["temp_pcb"])
)
hashboards[board["index"]].temp = sum(board_temp_data) / len(
board_temp_data
)
chip_temp_data = list(
filter(lambda x: not x == 0, board["temp_chip"])
)
hashboards[board["index"]].chip_temp = sum(chip_temp_data) / len(
chip_temp_data
)
if "S21+ Hyd" in self.model:
hashboards[board["index"]].inlet_temp = board["temp_pcb"][0]
hashboards[board["index"]].outlet_temp = board["temp_pcb"][2]
hashboards[board["index"]].chip_temp = board["temp_pic"][0]
board_temp_data = list(
filter(
lambda x: not x == 0,
[
board["temp_pic"][1],
board["temp_pic"][2],
board["temp_pic"][3],
board["temp_pcb"][1],
board["temp_pcb"][3],
],
)
)
hashboards[board["index"]].temp = (
sum(board_temp_data) / len(board_temp_data)
if len(board_temp_data) > 0
else 0
)
else:
board_temp_data = list(
filter(lambda x: not x == 0, board["temp_pcb"])
)
hashboards[board["index"]].temp = (
sum(board_temp_data) / len(board_temp_data)
if len(board_temp_data) > 0
else 0
)
chip_temp_data = list(
filter(lambda x: not x == 0, board["temp_chip"])
)
hashboards[board["index"]].chip_temp = (
sum(chip_temp_data) / len(chip_temp_data)
if len(chip_temp_data) > 0
else 0
)
hashboards[board["index"]].serial_number = board["sn"]
hashboards[board["index"]].missing = False
except LookupError:

View File

@@ -36,6 +36,15 @@ class S21Plus(AntMinerMake):
algo = MinerAlgo.SHA256
class S21PlusHydro(AntMinerMake):
raw_model = MinerModel.ANTMINER.S21PlusHydro
expected_chips = 95
expected_fans = 0
expected_hashboards = 3
algo = MinerAlgo.SHA256
class S21Pro(AntMinerMake):
raw_model = MinerModel.ANTMINER.S21Pro

View File

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

View File

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

View File

@@ -10,3 +10,12 @@ class AvalonNano3(AvalonMinerMake):
expected_chips = 10
expected_fans = 1
algo = MinerAlgo.SHA256
class AvalonNano3s(AvalonMinerMake):
raw_model = MinerModel.AVALONMINER.AvalonNano3s
expected_hashboards = 1
expected_chips = 12
expected_fans = 1
algo = MinerAlgo.SHA256

View File

@@ -121,6 +121,7 @@ MINER_CLASSES = {
"ANTMINER BHB68601": BMMinerS21, # ???
"ANTMINER BHB68606": BMMinerS21, # ???
"ANTMINER S21+": BMMinerS21Plus,
"ANTMINER S21+ HYD.": BMMinerS21PlusHydro,
"ANTMINER S21 PRO": BMMinerS21Pro,
"ANTMINER T21": BMMinerT21,
"ANTMINER S21 HYD.": BMMinerS21Hydro,
@@ -506,6 +507,7 @@ MINER_CLASSES = {
"AVALONMINER 1166PRO": CGMinerAvalon1166Pro,
"AVALONMINER 1246": CGMinerAvalon1246,
"AVALONMINER NANO3": CGMinerAvalonNano3,
"AVALON NANO3S": CGMinerAvalonNano3s,
"AVALONMINER 15-194": CGMinerAvalon1566,
},
MinerTypes.INNOSILICON: {
@@ -621,6 +623,7 @@ MINER_CLASSES = {
"ANTMINER S19 XP": LUXMinerS19XP,
"ANTMINER T19": LUXMinerT19,
"ANTMINER S21": LUXMinerS21,
"ANTMINER T21": LUXMinerT21,
},
MinerTypes.AURADINE: {
None: type("AuradineUnknown", (Auradine, AuradineMake), {}),

View File

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