Compare commits

...

18 Commits

Author SHA1 Message Date
Brett Rowan
55786b154d version: bump version number 2024-12-20 10:16:19 -07:00
Brett Rowan
6ab9681dec feature: add auto unit to hashrates and improve model dump for hashrates 2024-12-20 10:16:01 -07:00
Brett Rowan
89641c6316 version: bump version number 2024-12-20 09:26:16 -07:00
Brett Rowan
136ff6a688 feature: add generic hashrate type for doing sums 2024-12-20 09:25:52 -07:00
Brett Rowan
d918d93f4a version: bump version number 2024-12-20 09:21:32 -07:00
Brett Rowan
8046c532a6 bug: fix some issues with adding hashrate types 2024-12-20 09:21:06 -07:00
Upstream Data
92820a362d version: bump version number 2024-12-17 08:15:37 -07:00
aquariuslt
9fd90031a9 fix: update data.env_temp type to float 2024-12-17 08:14:59 -07:00
Brett Rowan
2f2223a112 version: bump version number 2024-12-16 20:06:33 -07:00
Wilfred Allyn
50e6cf9dfd feature: add supports_autotuning to vnish 2024-12-16 07:43:15 -07:00
Wilfred Allyn
1b5e3093e6 feature: set power to highest preset <= wattage 2024-12-16 07:43:15 -07:00
Wilfred Allyn
9e3578b4a2 feature: check vnish presets when set power 2024-12-16 07:43:15 -07:00
Wilfred Allyn
a3087e1a96 set vnish power limit 2024-12-16 07:43:15 -07:00
Brett Rowan
4b16ea2ca2 ci: update publish action 2024-12-10 13:55:30 -07:00
Upstream Data
5dd361c4ef version: bump version number 2024-12-10 13:05:20 -07:00
Upstream Data
098112742c bug: fix vnish config parsing in some overclocking cases 2024-12-10 13:04:58 -07:00
Upstream Data
cd31e0743e docs: update docs 2024-12-10 13:03:07 -07:00
Upstream Data
1a7d0bf7cc feature: add support for vnish S19k pro 2024-12-10 13:02:49 -07:00
14 changed files with 118 additions and 38 deletions

View File

@@ -13,10 +13,10 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4.2.2
- name: Publish GH release - name: Publish GH release
uses: softprops/action-gh-release@v0.1.14 uses: softprops/action-gh-release@v2.1.0
- name: Build using poetry and publish to PyPi - name: Build using poetry and publish to PyPi
uses: JRubics/poetry-publish@v1.11 uses: JRubics/poetry-publish@v2.0
with: with:
pypi_token: ${{ secrets.PYPI_API_KEY }} pypi_token: ${{ secrets.PYPI_API_KEY }}

View File

@@ -302,6 +302,13 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19k Pro (VNish)
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19kPro
handler: python
options:
show_root_heading: false
heading_level: 4
## T19 (VNish) ## T19 (VNish)
::: pyasic.miners.antminer.vnish.X19.T19.VNishT19 ::: pyasic.miners.antminer.vnish.X19.T19.VNishT19
handler: python handler: python

View File

@@ -533,6 +533,7 @@ details {
<li><a href="../antminer/X19#s19a-vnish">S19a (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#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#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> <li><a href="../antminer/X19#t19-vnish">T19 (VNish)</a></li>
</ul> </ul>
</details> </details>

View File

@@ -22,7 +22,7 @@ class MiningPreset(MinerConfigValue):
hashrate = None hashrate = None
else: else:
power = hr_power_split[0].replace("watt", "").strip() power = hr_power_split[0].replace("watt", "").strip()
hashrate = hr_power_split[1].replace("TH", "").strip() hashrate = hr_power_split[1].replace("TH", "").replace(" LC", "").strip()
tuned = web_preset["status"] == "tuned" tuned = web_preset["status"] == "tuned"
modded_psu = web_preset["modded_psu_required"] modded_psu = web_preset["modded_psu_required"]
return cls( return cls(

View File

@@ -13,19 +13,20 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import copy import copy
import time import time
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any, List, Union from typing import Any, List, Union
from pydantic import BaseModel, Field, computed_field, field_serializer from pydantic import BaseModel, Field, computed_field
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.config.mining import MiningModePowerTune from pyasic.config.mining import MiningModePowerTune
from pyasic.data.pools import PoolMetrics, Scheme from pyasic.data.pools import PoolMetrics, Scheme
from pyasic.device.algorithm.hashrate import AlgoHashRateType from pyasic.device.algorithm.hashrate import AlgoHashRateType
from pyasic.device.algorithm.hashrate.base import GenericHashrate
from ..device.algorithm.hashrate.unit.base import GenericUnit
from .boards import HashBoard from .boards import HashBoard
from .device import DeviceInfo from .device import DeviceInfo
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
@@ -96,7 +97,7 @@ class MinerData(BaseModel):
expected_fans: int | None = None expected_fans: int | None = None
# temperature # temperature
env_temp: int | None = None env_temp: float | None = None
# power # power
wattage: int | None = None wattage: int | None = None
@@ -135,9 +136,6 @@ class MinerData(BaseModel):
def fields(cls): def fields(cls):
return list(cls.model_fields.keys()) return list(cls.model_fields.keys())
def __post_init__(self):
self.raw_datetime = datetime.now(timezone.utc).astimezone()
def get(self, __key: str, default: Any = None): def get(self, __key: str, default: Any = None):
try: try:
val = self.__getitem__(__key) val = self.__getitem__(__key)
@@ -198,35 +196,23 @@ class MinerData(BaseModel):
@computed_field # type: ignore[misc] @computed_field # type: ignore[misc]
@property @property
def hashrate(self) -> AlgoHashRateType: def hashrate(self) -> AlgoHashRateType | None:
if len(self.hashboards) > 0: if len(self.hashboards) > 0:
hr_data = [] hr_data = []
for item in self.hashboards: for item in self.hashboards:
if item.hashrate is not None: if item.hashrate is not None:
hr_data.append(item.hashrate) hr_data.append(item.hashrate)
if len(hr_data) > 0: if len(hr_data) > 0:
return sum(hr_data, start=type(hr_data[0])(rate=0)) return sum(hr_data, start=self.hashboards[0].hashrate.__class__(rate=0))
return self.raw_hashrate return self.raw_hashrate
@field_serializer("hashrate")
def serialize_hashrate(self, hashrate: AlgoHashRateType | None) -> float:
if hashrate is not None:
return float(hashrate)
@field_serializer("expected_hashrate")
def serialize_expected_hashrate(
self, expected_hashrate: AlgoHashRateType | None, _info
) -> float:
if expected_hashrate is not None:
return float(expected_hashrate)
@hashrate.setter @hashrate.setter
def hashrate(self, val): def hashrate(self, val):
self.raw_hashrate = val self.raw_hashrate = val
@computed_field # type: ignore[misc] @computed_field # type: ignore[misc]
@property @property
def wattage_limit(self) -> int: def wattage_limit(self) -> int | None:
if self.config is not None: if self.config is not None:
if isinstance(self.config.mining_mode, MiningModePowerTune): if isinstance(self.config.mining_mode, MiningModePowerTune):
return self.config.mining_mode.power return self.config.mining_mode.power

View File

@@ -51,11 +51,6 @@ class HashBoard(BaseModel):
active: bool | None = None active: bool | None = None
voltage: float | None = None voltage: float | None = None
@field_serializer("hashrate")
def serialize_hashrate(self, hashrate: AlgoHashRateType | None) -> float:
if hashrate is not None:
return float(hashrate)
def get(self, __key: str, default: Any = None): def get(self, __key: str, default: Any = None):
try: try:
val = self.__getitem__(__key) val = self.__getitem__(__key)

View File

@@ -1,19 +1,42 @@
from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from pydantic import BaseModel from pydantic import BaseModel, field_serializer
from typing_extensions import Self from typing_extensions import Self
from .unit.base import AlgoHashRateUnitType from .unit.base import AlgoHashRateUnitType, GenericUnit
class AlgoHashRateType(BaseModel, ABC): class AlgoHashRateType(BaseModel, ABC):
unit: AlgoHashRateUnitType unit: AlgoHashRateUnitType
rate: float rate: float
@field_serializer("unit")
def serialize_unit(self, unit: AlgoHashRateUnitType):
return unit.model_dump()
@abstractmethod @abstractmethod
def into(self, other: "AlgoHashRateUnitType"): def into(self, other: "AlgoHashRateUnitType"):
pass pass
def auto_unit(self):
if 1 < self.rate // int(self.unit.H) < 1000:
return self.into(self.unit.H)
if 1 < self.rate // int(self.unit.MH) < 1000:
return self.into(self.unit.MH)
if 1 < self.rate // int(self.unit.GH) < 1000:
return self.into(self.unit.GH)
if 1 < self.rate // int(self.unit.TH) < 1000:
return self.into(self.unit.TH)
if 1 < self.rate // int(self.unit.PH) < 1000:
return self.into(self.unit.PH)
if 1 < self.rate // int(self.unit.EH) < 1000:
return self.into(self.unit.EH)
if 1 < self.rate // int(self.unit.ZH) < 1000:
return self.into(self.unit.ZH)
return self
def __float__(self): def __float__(self):
return float(self.rate) return float(self.rate)
@@ -27,36 +50,46 @@ class AlgoHashRateType(BaseModel, ABC):
return round(self.rate, n) return round(self.rate, n)
def __add__(self, other: Self | int | float) -> Self: def __add__(self, other: Self | int | float) -> Self:
if isinstance(other, self.__class__): if isinstance(other, AlgoHashRateType):
return self.__class__( return self.__class__(
rate=self.rate + other.into(self.unit).rate, unit=self.unit rate=self.rate + other.into(self.unit).rate, unit=self.unit
) )
return self.__class__(rate=self.rate + other, unit=self.unit) return self.__class__(rate=self.rate + other, unit=self.unit)
def __sub__(self, other: Self | int | float) -> Self: def __sub__(self, other: Self | int | float) -> Self:
if isinstance(other, self.__class__): if isinstance(other, AlgoHashRateType):
return self.__class__( return self.__class__(
rate=self.rate - other.into(self.unit).rate, unit=self.unit rate=self.rate - other.into(self.unit).rate, unit=self.unit
) )
return self.__class__(rate=self.rate - other, unit=self.unit) return self.__class__(rate=self.rate - other, unit=self.unit)
def __truediv__(self, other: Self | int | float) -> Self: def __truediv__(self, other: Self | int | float) -> Self:
if isinstance(other, self.__class__): if isinstance(other, AlgoHashRateType):
return self.__class__( return self.__class__(
rate=self.rate / other.into(self.unit).rate, unit=self.unit rate=self.rate / other.into(self.unit).rate, unit=self.unit
) )
return self.__class__(rate=self.rate / other, unit=self.unit) return self.__class__(rate=self.rate / other, unit=self.unit)
def __floordiv__(self, other: Self | int | float) -> Self: def __floordiv__(self, other: Self | int | float) -> Self:
if isinstance(other, self.__class__): if isinstance(other, AlgoHashRateType):
return self.__class__( return self.__class__(
rate=self.rate // other.into(self.unit).rate, unit=self.unit rate=self.rate // other.into(self.unit).rate, unit=self.unit
) )
return self.__class__(rate=self.rate // other, unit=self.unit) return self.__class__(rate=self.rate // other, unit=self.unit)
def __mul__(self, other: Self | int | float) -> Self: def __mul__(self, other: Self | int | float) -> Self:
if isinstance(other, self.__class__): if isinstance(other, AlgoHashRateType):
return self.__class__( return self.__class__(
rate=self.rate * other.into(self.unit).rate, unit=self.unit rate=self.rate * other.into(self.unit).rate, unit=self.unit
) )
return self.__class__(rate=self.rate * other, unit=self.unit) return self.__class__(rate=self.rate * other, unit=self.unit)
class GenericHashrate(AlgoHashRateType):
rate: float = 0
unit: GenericUnit = GenericUnit.H
def into(self, other: GenericUnit):
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -53,3 +53,19 @@ class AlgoHashRateUnitType(IntEnum):
def __repr__(self): def __repr__(self):
return str(self) return str(self)
def model_dump(self):
return {"value": self.value, "suffix": str(self)}
class GenericUnit(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 = H

View File

@@ -22,6 +22,7 @@ from pyasic.miners.device.models import (
S19aPro, S19aPro,
S19j, S19j,
S19jPro, S19jPro,
S19kPro,
S19NoPIC, S19NoPIC,
S19Pro, S19Pro,
S19ProHydro, S19ProHydro,
@@ -62,3 +63,7 @@ class VNishS19jPro(VNish, S19jPro):
class VNishS19ProHydro(VNish, S19ProHydro): class VNishS19ProHydro(VNish, S19ProHydro):
pass pass
class VNishS19kPro(VNish, S19kPro):
pass

View File

@@ -20,6 +20,7 @@ from .S19 import (
VNishS19aPro, VNishS19aPro,
VNishS19j, VNishS19j,
VNishS19jPro, VNishS19jPro,
VNishS19kPro,
VNishS19NoPIC, VNishS19NoPIC,
VNishS19Pro, VNishS19Pro,
VNishS19ProHydro, VNishS19ProHydro,

View File

@@ -14,6 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import logging
from typing import Optional from typing import Optional
from pyasic import MinerConfig from pyasic import MinerConfig
@@ -96,6 +97,7 @@ class VNish(VNishFirmware, BMMiner):
supports_shutdown = True supports_shutdown = True
supports_presets = True supports_presets = True
supports_autotuning = True
data_locations = VNISH_DATA_LOC data_locations = VNISH_DATA_LOC
@@ -272,3 +274,27 @@ class VNish(VNishFirmware, BMMiner):
return self.config return self.config
self.config = MinerConfig.from_vnish(web_settings, web_presets) self.config = MinerConfig.from_vnish(web_settings, web_presets)
return self.config return self.config
async def set_power_limit(self, wattage: int) -> bool:
config = await self.get_config()
valid_presets = [
preset.power
for preset in config.mining_mode.available_presets
if preset.tuned and preset.power <= wattage
]
new_wattage = max(valid_presets)
# Set power to highest preset <= wattage
try:
await self.web.set_power_limit(new_wattage)
updated_settings = await self.web.settings()
except APIError:
raise
except Exception as e:
logging.warning(f"{self} - Failed to set power limit: {e}")
return False
if int(updated_settings["miner"]["overclock"]["preset"]) == new_wattage:
return True
else:
return False

View File

@@ -407,6 +407,7 @@ MINER_CLASSES = {
"ANTMINER S19A": VNishS19a, "ANTMINER S19A": VNishS19a,
"ANTMINER S19A PRO": VNishS19aPro, "ANTMINER S19A PRO": VNishS19aPro,
"ANTMINER S19 PRO HYD.": VNishS19ProHydro, "ANTMINER S19 PRO HYD.": VNishS19ProHydro,
"ANTMINER S19K PRO": VNishS19kPro,
"ANTMINER T19": VNishT19, "ANTMINER T19": VNishT19,
"ANTMINER S21": VNishS21, "ANTMINER S21": VNishS21,
}, },

View File

@@ -138,6 +138,15 @@ class VNishWebAPI(BaseWebAPI):
async def settings(self) -> dict: async def settings(self) -> dict:
return await self.send_command("settings") return await self.send_command("settings")
async def set_power_limit(self, wattage: int) -> bool:
# Can only set power limit to tuned preset
settings = await self.settings()
settings["miner"]["overclock"]["preset"] = str(wattage)
new_settings = {"miner": {"overclock": settings["miner"]["overclock"]}}
# response will always be {"restart_required":false,"reboot_required":false} even if unsuccessful
return await self.send_command("settings", privileged=True, **new_settings)
async def autotune_presets(self) -> dict: async def autotune_presets(self) -> dict:
return await self.send_command("autotune/presets") return await self.send_command("autotune/presets")

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "pyasic" name = "pyasic"
version = "0.65.7" version = "0.67.0"
description = "A simplified and standardized interface for Bitcoin ASICs." description = "A simplified and standardized interface for Bitcoin ASICs."
authors = ["UpstreamData <brett@upstreamdata.ca>"] authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic" repository = "https://github.com/UpstreamData/pyasic"