Compare commits

...

24 Commits

Author SHA1 Message Date
Upstream Data
ba6a1606b6 version: bump version number. 2024-04-12 08:48:52 -06:00
Upstream Data
51b0c0456f bug: fix parsing of tuner algo modes into json. 2024-04-12 08:47:40 -06:00
Upstream Data
04bd03b496 version: bump version number. 2024-04-11 09:31:00 -06:00
Upstream Data
bc5764c8ff feature: add stock support for T21. 2024-04-11 09:30:24 -06:00
Brett Rowan
1f2e066f4c Merge pull request #122 from jpcomps/fix_is_mining
feature: add is_mining for ePIC, add T21, add BlockMiner 720i
2024-04-11 09:28:18 -06:00
Upstream Data
74c457a694 version: bump version number. 2024-04-11 09:23:55 -06:00
Upstream Data
4a1c53dfd7 bug: fix some issues with config on bosminer. 2024-04-11 09:23:33 -06:00
John-Paul Compagnone
81b77f8768 cleaner code 2024-04-11 11:06:45 -04:00
John-Paul Compagnone
3b127b6862 fix is_mining for ePIC 2024-04-11 09:34:40 -04:00
Upstream Data
2815d2ba11 version: bump version number. 2024-04-08 12:15:38 -06:00
Upstream Data
1ff20fc6f0 feature: add bosminer S21 support. 2024-04-08 12:15:18 -06:00
Brett Rowan
797c847055 version: bump version number. 2024-04-07 17:17:17 -06:00
Brett Rowan
65c7f2f66f bug: fix vnish shutting-down handling. 2024-04-07 17:16:37 -06:00
Brett Rowan
445d621590 version: bump version number. 2024-04-07 17:04:10 -06:00
Brett Rowan
d39ecfd6b4 feature: add is_mining for vnish. 2024-04-07 17:03:28 -06:00
Brett Rowan
36663471fb version: bump version number. 2024-04-06 12:34:43 -06:00
Brett Rowan
80293ac52f bug: fix incorrect model for XP. 2024-04-06 12:34:14 -06:00
UpstreamData
70b45f40f5 docs: update dev setup. 2024-03-29 11:19:17 -06:00
UpstreamData
a511fabd9c Update README.md with dev docs. 2024-03-29 11:17:39 -06:00
Jim Burtoft
8bc8f6f178 Comment on some code I didn't understand initially (#118) 2024-03-28 21:31:37 -06:00
Brett Rowan
b790ad58a7 version: bump version number. 2024-03-23 20:27:42 -06:00
Brett Rowan
354ab793a2 bug: fix vnish MAC not working on some versions. 2024-03-23 20:25:26 -06:00
JP Compagnone
577e8df612 Merge branch 'UpstreamData:master' into master 2024-03-14 16:58:12 -04:00
John-Paul Compagnone
743823f66e add Blockminer 720i and Antminer T21 support 2024-02-25 17:44:20 -05:00
26 changed files with 273 additions and 35 deletions

View File

@@ -23,6 +23,10 @@ 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
```
poetry install
```
- [venv](https://docs.python.org/3/library/venv.html): included in Python standard library but has fewer features than other options
- [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv): [pyenv](https://github.com/pyenv/pyenv) plugin for managing virtualenvs
```
@@ -36,6 +40,12 @@ It is recommended to install `pyasic` in a [virtual environment](https://realpyt
`python -m pip install .` or `poetry install`
##### Additional Developer Setup
```
poetry install --with dev
pre-commit install
```
---
## Getting started

View File

@@ -106,7 +106,7 @@
show_root_heading: false
heading_level: 4
## S19 Pro+ Hydro
## S19K Pro
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19KPro
handler: python
options:

View File

@@ -8,6 +8,13 @@
show_root_heading: false
heading_level: 4
## S21
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21
handler: python
options:
show_root_heading: false
heading_level: 4
## S21 (ePIC)
::: pyasic.miners.antminer.epic.X21.S21.ePICS21
handler: python

View File

@@ -81,7 +81,7 @@ details {
<li><a href="../antminer/X19#s19-hydro">S19 Hydro</a></li>
<li><a href="../antminer/X19#s19-pro-hydro">S19 Pro Hydro</a></li>
<li><a href="../antminer/X19#s19-pro_1-hydro">S19 Pro+ Hydro</a></li>
<li><a href="../antminer/X19#s19-pro_1-hydro">S19 Pro+ Hydro</a></li>
<li><a href="../antminer/X19#s19k-pro">S19K Pro</a></li>
<li><a href="../antminer/X19#t19">T19</a></li>
</ul>
</details>
@@ -460,6 +460,12 @@ details {
<li><a href="../antminer/X19#t19">T19</a></li>
</ul>
</details>
<details>
<summary>X21 Series:</summary>
<ul>
<li><a href="../antminer/X21#s21">S21</a></li>
</ul>
</details>
</ul>
</details>
<details>

View File

@@ -126,6 +126,7 @@ class MiningModeHPM(MinerConfigValue):
return {"mode": {"mode": "turbo"}}
@dataclass
class StandardTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
@@ -133,20 +134,22 @@ class StandardTuneAlgo(MinerConfigValue):
return VOptAlgo().as_epic()
@dataclass
class VOptAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
mode: str = field(init=False, default="voltage_optimizer")
def as_epic(self) -> str:
return "VoltageOptimizer"
class ChipTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
mode: str = field(init=False, default="chip_tune")
def as_epic(self) -> str:
return "ChipTune"
@dataclass
class TunerAlgo(MinerConfigOption):
standard = StandardTuneAlgo
voltage_optimizer = VOptAlgo
@@ -156,6 +159,16 @@ class TunerAlgo(MinerConfigOption):
def default(cls):
return cls.standard()
@classmethod
def from_dict(cls, dict_conf: dict | None):
mode = dict_conf.get("mode")
if mode is None:
return cls.default()
cls_attr = getattr(cls, mode)
if cls_attr is not None:
return cls_attr().from_dict(dict_conf)
@dataclass
class MiningModePowerTune(MinerConfigValue):
@@ -169,7 +182,7 @@ class MiningModePowerTune(MinerConfigValue):
if dict_conf.get("power"):
cls_conf["power"] = dict_conf["power"]
if dict_conf.get("algo"):
cls_conf["algo"] = dict_conf["algo"]
cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"])
return cls(**cls_conf)
@@ -184,7 +197,10 @@ class MiningModePowerTune(MinerConfigValue):
return {}
def as_bosminer(self) -> dict:
return {"autotuning": {"enabled": True, "psu_power_limit": self.power}}
conf = {"enabled": True, "mode": "power_target"}
if self.power is not None:
conf["power_target"] = self.power
return {"autotuning": conf}
def as_boser(self) -> dict:
return {
@@ -212,13 +228,25 @@ class MiningModeHashrateTune(MinerConfigValue):
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune":
return cls(dict_conf.get("hashrate"))
cls_conf = {}
if dict_conf.get("hashrate"):
cls_conf["hashrate"] = dict_conf["hashrate"]
if dict_conf.get("algo"):
cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"])
return cls(**cls_conf)
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_bosminer(self) -> dict:
conf = {"enabled": True, "mode": "hashrate_target"}
if self.hashrate is not None:
conf["hashrate_target"] = self.hashrate
return {"autotuning": conf}
def as_boser(self) -> dict:
return {
"set_performance_mode": SetPerformanceModeRequest(

View File

@@ -114,6 +114,8 @@ class PowerScalingEnabled(MinerConfigValue):
def from_bosminer(cls, power_scaling_conf: dict) -> "PowerScalingEnabled":
power_step = power_scaling_conf.get("power_step")
min_power = power_scaling_conf.get("min_psu_power_limit")
if min_power is None:
min_power = power_scaling_conf.get("min_power_target")
sd_mode = PowerScalingShutdown.from_bosminer(power_scaling_conf)
return cls(
@@ -138,12 +140,12 @@ class PowerScalingEnabled(MinerConfigValue):
if self.power_step is not None:
cfg["power_step"] = self.power_step
if self.minimum_power is not None:
cfg["min_psu_power_limit"] = self.minimum_power
cfg["min_power_target"] = self.minimum_power
if self.shutdown_enabled is not None:
cfg = {**cfg, **self.shutdown_enabled.as_bosminer()}
return {"power_scaling": cfg}
return {"performance_scaling": cfg}
def as_boser(self) -> dict:
return {
@@ -189,6 +191,8 @@ class PowerScalingConfig(MinerConfigOption):
@classmethod
def from_bosminer(cls, toml_conf: dict):
power_scaling = toml_conf.get("power_scaling")
if power_scaling is None:
power_scaling = toml_conf.get("performance_scaling")
if power_scaling is not None:
enabled = power_scaling.get("enabled")
if enabled is not None:

View File

@@ -94,7 +94,14 @@ class MinerData:
percent_expected_wattage: float = field(init=False)
nominal: bool = field(init=False)
config: MinerConfig = None
errors: List[Union[WhatsminerError, BraiinsOSError, X19Error, InnosiliconError]] = field(default_factory=list)
errors: List[
Union[
WhatsminerError,
BraiinsOSError,
X19Error,
InnosiliconError,
]
] = field(default_factory=list)
fault_light: Union[bool, None] = None
efficiency: int = field(init=False)
is_mining: bool = True

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 AntminerModern
from pyasic.miners.models import T21
class BMMinerT21(AntminerModern, T21):
pass

View File

@@ -14,3 +14,4 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .S21 import BMMinerS21
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 BOSer
from pyasic.miners.models import S21
class BOSMinerS21(BOSer, S21):
pass

View File

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

View File

@@ -17,3 +17,4 @@
from .X9 import *
from .X17 import *
from .X19 import *
from .X21 import *

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 ePIC
from pyasic.miners.models import T21
class ePICT21(ePIC, T21):
pass

View File

@@ -17,3 +17,7 @@
from .S21 import (
ePICS21,
)
from .T21 import (
ePICT21,
)

View File

@@ -181,16 +181,17 @@ class BOSMiner(BaseMiner):
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
self.config = config
parsed_cfg = config.as_bosminer(user_suffix=user_suffix)
toml_conf = toml.dumps(
{
"format": {
"version": "1.2+",
"version": "2.0",
"generator": "pyasic",
"model": f"{self.make.replace('Miner', 'miner')} {self.raw_model.replace('j', 'J')}",
"timestamp": int(time.time()),
},
**config.as_bosminer(user_suffix=user_suffix),
**parsed_cfg,
}
)
try:

View File

@@ -74,6 +74,10 @@ EPIC_DATA_LOC = DataLocations(
"_get_uptime",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.IS_MINING): DataFunction(
"_is_mining",
[WebAPICommand("web_summary", "summary")],
),
}
)
@@ -313,8 +317,18 @@ class ePIC(BaseMiner):
hb_list[hb["Index"]].temp = hb["Temperature"]
return hb_list
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def _is_mining(self, web_summary, *args, **kwargs) -> Optional[bool]:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
pass
if web_summary is not None:
try:
op_state = web_summary["Status"]["Operating State"]
return not op_state == "Idling"
except KeyError:
pass
async def _get_uptime(self, web_summary: dict = None) -> Optional[int]:
if web_summary is None:

View File

@@ -74,6 +74,10 @@ VNISH_DATA_LOC = DataLocations(
"_get_uptime",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.IS_MINING): DataFunction(
"_is_mining",
[WebAPICommand("web_summary", "summary")],
),
}
)
@@ -127,16 +131,6 @@ class VNish(BMMiner):
return False
async def _get_mac(self, web_summary: dict = None) -> str:
if web_summary is None:
web_info = await self.web.info()
if web_info is not None:
try:
mac = web_info["system"]["network_status"]["mac"]
return mac
except KeyError:
pass
if web_summary is not None:
try:
mac = web_summary["system"]["network_status"]["mac"]
@@ -144,6 +138,15 @@ class VNish(BMMiner):
except KeyError:
pass
web_info = await self.web.info()
if web_info is not None:
try:
mac = web_info["system"]["network_status"]["mac"]
return mac
except KeyError:
pass
async def _get_hostname(self, web_summary: dict = None) -> str:
if web_summary is None:
web_info = await self.web.info()
@@ -216,6 +219,20 @@ class VNish(BMMiner):
except LookupError:
return fw_ver
async def _is_mining(self, web_summary: dict = None) -> Optional[bool]:
if web_summary is None:
web_summary = await self.web.summary()
if web_summary is not None:
try:
is_mining = not web_summary["miner"]["miner_status"]["miner_state"] in [
"stopped",
"shutting-down",
]
return is_mining
except LookupError:
pass
async def get_config(self) -> MinerConfig:
try:
web_settings = await self.web.settings()

View File

@@ -16,4 +16,5 @@
from .blockminer import (
ePICBlockMiner520i,
ePICBlockMiner720i,
)

View File

@@ -16,7 +16,12 @@
from pyasic.miners.backends import ePIC
from pyasic.miners.models import BlockMiner520i
from pyasic.miners.models import BlockMiner720i
class ePICBlockMiner520i(ePIC, BlockMiner520i):
pass
class ePICBlockMiner720i(ePIC, BlockMiner720i):
pass

View File

@@ -105,6 +105,7 @@ MINER_CLASSES = {
"ANTMINER S19K PRO": BMMinerS19KPro,
"ANTMINER T19": BMMinerT19,
"ANTMINER S21": BMMinerS21,
"ANTMINER T21": BMMinerT21,
},
MinerTypes.WHATSMINER: {
None: type("WhatsminerUnknown", (BTMiner, WhatsMinerMake), {}),
@@ -372,8 +373,9 @@ MINER_CLASSES = {
"ANTMINER S19J PRO NOPIC": BOSMinerS19jPro,
"ANTMINER S19J PRO+": BOSMinerS19jProPlus,
"ANTMINER S19K PRO NOPIC": BOSMinerS19kProNoPIC,
"ANTMINER S19XP": BOSMinerS19XP,
"ANTMINER S19 XP": BOSMinerS19XP,
"ANTMINER T19": BOSMinerT19,
"ANTMINER S21": BOSMinerS21,
},
MinerTypes.VNISH: {
None: VNish,
@@ -401,7 +403,9 @@ MINER_CLASSES = {
"ANTMINER S19K PRO": ePICS19kPro,
"ANTMINER S19 XP": ePICS19XP,
"ANTMINER S21": ePICS21,
"ANTMINER T21": ePICT21,
"BLOCKMINER 520I": ePICBlockMiner520i,
"BLOCKMINER 720I": ePICBlockMiner720i,
},
MinerTypes.HIVEON: {
None: Hiveon,
@@ -915,10 +919,11 @@ class MinerFactory:
async def get_miner_model_braiins_os(self, ip: str) -> str | None:
sock_json_data = await self.send_api_command(ip, "devdetails")
try:
miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace(
"Bitmain ", ""
miner_model = (
sock_json_data["DEVDETAILS"][0]["Model"]
.replace("Bitmain ", "")
.replace("S19XP", "S19 XP")
)
return miner_model
except (TypeError, LookupError):
pass
@@ -931,7 +936,9 @@ class MinerFactory:
)
if d.status_code == 200:
json_data = d.json()
miner_model = json_data["data"]["bosminer"]["info"]["modelName"]
miner_model = json_data["data"]["bosminer"]["info"][
"modelName"
].replace("S19XP", "S19 XP")
return miner_model
except (httpx.HTTPError, LookupError):
pass

View File

@@ -0,0 +1,23 @@
# ------------------------------------------------------------------------------
# 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.makes import AntMinerMake
class T21(AntMinerMake):
raw_model = "T21"
expected_chips = 108
expected_fans = 4

View File

@@ -17,3 +17,6 @@
from .S21 import (
S21,
)
from .T21 import (
T21,
)

View File

@@ -5,3 +5,9 @@ class BlockMiner520i(ePICMake):
raw_model = "BlockMiner 520i"
expected_chips = 124
expected_fans = 4
class BlockMiner720i(ePICMake):
raw_model = "BlockMiner 720i"
expected_chips = 180
expected_fans = 4

View File

@@ -46,7 +46,9 @@ _settings = { # defaults
ssl_cxt = httpx.create_ssl_context()
#this function configures socket options like SO_LINGER and returns an AsyncHTTPTransport instance to perform asynchronous HTTP requests
#using those options.
#SO_LINGER controls what happens when you close a socket with unsent data - it allows specifying linger time for the data to be sent.
def transport(verify: Union[str, bool, SSLContext] = ssl_cxt):
l_onoff = 1
l_linger = get("so_linger_time", 1000)

View File

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

View File

@@ -72,7 +72,11 @@ class TestConfig(unittest.TestCase):
},
"fan_mode": {"mode": "manual", "speed": 90, "minimum_fans": 2},
"temperature": {"target": 70, "hot": None, "danger": 120},
"mining_mode": {"mode": "power_tuning", "power": 3000, "algo": {}},
"mining_mode": {
"mode": "power_tuning",
"power": 3000,
"algo": {"mode": "standard"},
},
"power_scaling": {
"mode": "enabled",
"power_step": 100,
@@ -98,7 +102,11 @@ class TestConfig(unittest.TestCase):
"dangerous_temp": 120,
},
"fan_control": {"min_fans": 2, "speed": 90},
"autotuning": {"enabled": True, "psu_power_limit": 3000},
"autotuning": {
"enabled": True,
"mode": "power_target",
"power_target": 3000,
},
"group": [
{
"name": "W91Q1L",
@@ -112,10 +120,10 @@ class TestConfig(unittest.TestCase):
"quota": 1,
}
],
"power_scaling": {
"performance_scaling": {
"enabled": True,
"power_step": 100,
"min_psu_power_limit": 2000,
"min_power_target": 2000,
"shutdown_enabled": True,
"shutdown_duration": 3,
},