Compare commits

...

15 Commits

Author SHA1 Message Date
UpstreamData
2c3b5599fe version: bump version number. 2023-10-02 09:20:24 -06:00
UpstreamData
e421eaa324 feature: add support for M20P, and add chips for M20SV30. 2023-10-02 09:20:01 -06:00
UpstreamData
53f3fc5ee9 version: bump version number. 2023-09-28 15:47:49 -06:00
UpstreamData
1b36de4131 bug: fAdd new commands added in whatsminer API 2.0.5. 2023-09-28 15:47:20 -06:00
UpstreamData
6f0c6f6284 bug: fix whatsminer identification to work with backwards incompatible changes in API 2.0.5. 2023-09-28 15:42:12 -06:00
UpstreamData
b7dda5bf87 Update README.md 2023-09-26 11:50:56 -06:00
UpstreamData
53a3bbf531 version: bump version number. 2023-09-19 13:59:56 -06:00
UpstreamData
50586f1ce7 feature: add S19+. 2023-09-19 13:59:03 -06:00
UpstreamData
9f6235a0fc feature: add S19i. 2023-09-19 13:56:40 -06:00
UpstreamData
4d21f150ce version: bump version number. 2023-09-18 09:35:38 -06:00
UpstreamData
7c0dfc49dd bug: fix wrong fault light setting when setting fault light to off. 2023-09-18 09:35:19 -06:00
UpstreamData
269b13f6c1 version: bump version number. 2023-09-15 08:57:56 -06:00
Elias Kunnas
a9bb7d2e5a Fix btminer pre_power_on (#62) 2023-09-15 08:56:29 -06:00
Upstream Data
11295f27a7 version: bump version number. 2023-09-12 19:21:04 -06:00
Upstream Data
55aa3dd85b bug: handle edge cases where a missed get_config on bosminer can cause an empty config to be applied to a miner. 2023-09-12 19:20:48 -06:00
17 changed files with 156 additions and 25 deletions

View File

@@ -1,5 +1,5 @@
# pyasic # pyasic
*A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.* *A simplified and standardized interface for Bitcoin ASICs.*
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![pypi](https://img.shields.io/pypi/v/pyasic.svg)](https://pypi.org/project/pyasic/) [![pypi](https://img.shields.io/pypi/v/pyasic.svg)](https://pypi.org/project/pyasic/)

View File

@@ -22,7 +22,7 @@ import hashlib
import json import json
import logging import logging
import re import re
from typing import Union from typing import Literal, Union
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from passlib.handlers.md5_crypt import md5_crypt from passlib.handlers.md5_crypt import md5_crypt
@@ -40,6 +40,12 @@ from pyasic.settings import PyasicSettings
# you change the password, you can pass that to this class as pwd, # you change the password, you can pass that to this class as pwd,
# or add it as the Whatsminer_pwd in the settings.toml file. # or add it as the Whatsminer_pwd in the settings.toml file.
PrePowerOnMessage = Union[
Literal["wait for adjust temp"],
Literal["adjust complete"],
Literal["adjust continue"],
]
def _crypt(word: str, salt: str) -> str: def _crypt(word: str, salt: str) -> str:
"""Encrypts a word with a salt, using a standard salt format. """Encrypts a word with a salt, using a standard salt format.
@@ -693,7 +699,7 @@ class BTMinerAPI(BaseMinerAPI):
) )
return await self.send_privileged_command("set_power_pct", percent=str(percent)) return await self.send_privileged_command("set_power_pct", percent=str(percent))
async def pre_power_on(self, complete: bool, msg: str) -> dict: async def pre_power_on(self, complete: bool, msg: PrePowerOnMessage) -> dict:
"""Configure or check status of pre power on. """Configure or check status of pre power on.
<details> <details>
@@ -713,7 +719,7 @@ class BTMinerAPI(BaseMinerAPI):
</details> </details>
""" """
if not msg == "wait for adjust temp" or "adjust complete" or "adjust continue": if msg not in ("wait for adjust temp", "adjust complete", "adjust continue"):
raise APIError( raise APIError(
"Message is incorrect, please choose one of " "Message is incorrect, please choose one of "
'["wait for adjust temp", ' '["wait for adjust temp", '
@@ -729,6 +735,34 @@ class BTMinerAPI(BaseMinerAPI):
) )
### ADDED IN V2.0.5 Whatsminer API ### ### ADDED IN V2.0.5 Whatsminer API ###
@api_min_version("2.0.5")
async def set_power_pct_v2(self, percent: int) -> dict:
"""Set the power percentage of the miner based on current power. Used for temporary adjustment. Added in API v2.0.5.
<details>
<summary>Expand</summary>
Set the power percentage of the miner, only works after changing
the password of the miner using the Whatsminer tool.
Parameters:
percent: The power percentage to set.
Returns:
A reply informing of the status of setting the power percentage.
</details>
"""
if not 0 < percent < 100:
raise APIError(
f"Power PCT % is outside of the allowed "
f"range. Please set a % between 0 and "
f"100"
)
return await self.send_privileged_command(
"set_power_pct_v2", percent=str(percent)
)
@api_min_version("2.0.5") @api_min_version("2.0.5")
async def set_temp_offset(self, temp_offset: int) -> dict: async def set_temp_offset(self, temp_offset: int) -> dict:
"""Set the offset of miner hash board target temperature. """Set the offset of miner hash board target temperature.

View File

@@ -21,9 +21,11 @@ from pyasic.miners.types import (
S19XP, S19XP,
S19a, S19a,
S19aPro, S19aPro,
S19i,
S19j, S19j,
S19jNoPIC, S19jNoPIC,
S19jPro, S19jPro,
S19Plus,
S19Pro, S19Pro,
S19ProPlus, S19ProPlus,
) )
@@ -33,6 +35,14 @@ class BMMinerS19(AntminerModern, S19):
pass pass
class BMMinerS19Plus(AntminerModern, S19Plus):
pass
class BMMinerS19i(AntminerModern, S19i):
pass
class BMMinerS19Pro(AntminerModern, S19Pro): class BMMinerS19Pro(AntminerModern, S19Pro):
pass pass

View File

@@ -18,10 +18,12 @@ from .S19 import (
BMMinerS19, BMMinerS19,
BMMinerS19a, BMMinerS19a,
BMMinerS19aPro, BMMinerS19aPro,
BMMinerS19i,
BMMinerS19j, BMMinerS19j,
BMMinerS19jNoPIC, BMMinerS19jNoPIC,
BMMinerS19jPro, BMMinerS19jPro,
BMMinerS19L, BMMinerS19L,
BMMinerS19Plus,
BMMinerS19Pro, BMMinerS19Pro,
BMMinerS19ProPlus, BMMinerS19ProPlus,
BMMinerS19XP, BMMinerS19XP,

View File

@@ -109,7 +109,7 @@ class AntminerModern(BMMiner):
data = await self.web.blink(blink=False) data = await self.web.blink(blink=False)
if data: if data:
if data.get("code") == "B100": if data.get("code") == "B100":
self.light = True self.light = False
return self.light return self.light
async def reboot(self) -> bool: async def reboot(self) -> bool:

View File

@@ -239,14 +239,16 @@ class BMMiner(BaseMiner):
for i in range(board_offset, board_offset + 4): for i in range(board_offset, board_offset + 4):
try: try:
key = f'chain_acs{i}' key = f"chain_acs{i}"
if boards[1].get(key, '') != '': if boards[1].get(key, "") != "":
real_slots.append(i) real_slots.append(i)
except LookupError: except LookupError:
pass pass
if len(real_slots) < 3: if len(real_slots) < 3:
real_slots = list(range(board_offset, board_offset + self.ideal_hashboards)) real_slots = list(
range(board_offset, board_offset + self.ideal_hashboards)
)
for i in real_slots: for i in real_slots:
hashboard = HashBoard( hashboard = HashBoard(

View File

@@ -303,17 +303,12 @@ class BOSMiner(BaseMiner):
The config from `self.config`. The config from `self.config`.
""" """
logging.debug(f"{self}: Getting config.") logging.debug(f"{self}: Getting config.")
conn = None
try: try:
conn = await self._get_ssh_connection() conn = await self._get_ssh_connection()
except ConnectionError: except ConnectionError:
try: conn = None
pools = await self.api.pools()
except APIError:
return self.config
if pools:
self.config = MinerConfig().from_api(pools["POOLS"])
return self.config
if conn: if conn:
async with conn: async with conn:
# good ol' BBB compatibility :/ # good ol' BBB compatibility :/
@@ -365,6 +360,8 @@ class BOSMiner(BaseMiner):
async def set_power_limit(self, wattage: int) -> bool: async def set_power_limit(self, wattage: int) -> bool:
try: try:
cfg = await self.get_config() cfg = await self.get_config()
if cfg is None:
return False
cfg.autotuning_wattage = wattage cfg.autotuning_wattage = wattage
await self.send_config(cfg) await self.send_config(cfg)
except Exception as e: except Exception as e:

View File

@@ -85,6 +85,8 @@ MINER_CLASSES = {
"ANTMINER S19L": BMMinerS19L, "ANTMINER S19L": BMMinerS19L,
"ANTMINER S19 PRO": BMMinerS19Pro, "ANTMINER S19 PRO": BMMinerS19Pro,
"ANTMINER S19J": BMMinerS19j, "ANTMINER S19J": BMMinerS19j,
"ANTMINER S19I": BMMinerS19i,
"ANTMINER S19+": BMMinerS19Plus,
"ANTMINER S19J88NOPIC": BMMinerS19jNoPIC, "ANTMINER S19J88NOPIC": BMMinerS19jNoPIC,
"ANTMINER S19PRO+": BMMinerS19ProPlus, "ANTMINER S19PRO+": BMMinerS19ProPlus,
"ANTMINER S19J PRO": BMMinerS19jPro, "ANTMINER S19J PRO": BMMinerS19jPro,
@@ -99,6 +101,8 @@ MINER_CLASSES = {
"M20SV10": BTMinerM20SV10, "M20SV10": BTMinerM20SV10,
"M20SV20": BTMinerM20SV20, "M20SV20": BTMinerM20SV20,
"M20SV30": BTMinerM20SV30, "M20SV30": BTMinerM20SV30,
"M20PV10": BTMinerM20PV10,
"M20PV30": BTMinerM20PV30,
"M20S+V30": BTMinerM20SPlusV30, "M20S+V30": BTMinerM20SPlusV30,
"M21V10": BTMinerM21V10, "M21V10": BTMinerM21V10,
"M21SV20": BTMinerM21SV20, "M21SV20": BTMinerM21SV20,
@@ -751,7 +755,7 @@ class MinerFactory:
async def get_miner_model_whatsminer(self, ip: str): async def get_miner_model_whatsminer(self, ip: str):
sock_json_data = await self.send_api_command(ip, "devdetails") sock_json_data = await self.send_api_command(ip, "devdetails")
try: try:
miner_model = sock_json_data["DEVDETAILS"][0]["Model"] miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace("_", "")
return miner_model return miner_model
except (TypeError, LookupError): except (TypeError, LookupError):
@@ -777,11 +781,13 @@ class MinerFactory:
) )
auth = auth_req.json()["jwt"] auth = auth_req.json()["jwt"]
web_data = (await session.post( web_data = (
await session.post(
f"http://{ip}/api/type", f"http://{ip}/api/type",
headers={"Authorization": "Bearer " + auth}, headers={"Authorization": "Bearer " + auth},
data={}, data={},
)).json() )
).json()
return web_data["type"] return web_data["type"]
except (httpx.HTTPError, LookupError): except (httpx.HTTPError, LookupError):
pass pass

View File

@@ -44,6 +44,24 @@ class S19Pro(AntMiner): # noqa - ignore ABC method implementation
self.fan_count = 4 self.fan_count = 4
class S19i(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19i"
self.nominal_chips = 80
self.fan_count = 4
class S19Plus(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19+"
self.nominal_chips = 80
self.fan_count = 4
class S19ProPlus(AntMiner): # noqa - ignore ABC method implementation class S19ProPlus(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"): def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver) super().__init__(ip, api_ver)

View File

@@ -20,10 +20,12 @@ from .S19 import (
S19XP, S19XP,
S19a, S19a,
S19aPro, S19aPro,
S19i,
S19j, S19j,
S19jNoPIC, S19jNoPIC,
S19jPro, S19jPro,
S19NoPIC, S19NoPIC,
S19Plus,
S19Pro, S19Pro,
S19ProPlus, S19ProPlus,
) )

View File

@@ -0,0 +1,35 @@
# ------------------------------------------------------------------------------
# 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 WhatsMiner
class M20PV10(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "M20P V10"
self.nominal_chips = 156
self.fan_count = 2
class M20PV30(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "M20P V30"
self.nominal_chips = 148
self.fan_count = 2

View File

@@ -42,8 +42,5 @@ class M20SV30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M20S V30" self.model = "M20S V30"
self.nominal_chips = 0 self.nominal_chips = 140
warnings.warn(
"Unknown chip count for miner type M20SV30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
)
self.fan_count = 2 self.fan_count = 2

View File

@@ -15,6 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .M20 import M20V10 from .M20 import M20V10
from .M20P import M20PV10, M20PV30
from .M20S import M20SV10, M20SV20, M20SV30 from .M20S import M20SV10, M20SV20, M20SV30
from .M20S_Plus import M20SPlusV30 from .M20S_Plus import M20SPlusV30
from .M21 import M21V10 from .M21 import M21V10

View File

@@ -0,0 +1,26 @@
# ------------------------------------------------------------------------------
# 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 M2X
from pyasic.miners.types import M20PV10, M20PV30
class BTMinerM20PV10(M2X, M20PV10):
pass
class BTMinerM20PV30(M2X, M20PV30):
pass

View File

@@ -15,6 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .M20 import BTMinerM20V10 from .M20 import BTMinerM20V10
from .M20P import BTMinerM20PV10, BTMinerM20PV30
from .M20S import BTMinerM20SV10, BTMinerM20SV20, BTMinerM20SV30 from .M20S import BTMinerM20SV10, BTMinerM20SV20, BTMinerM20SV30
from .M20S_Plus import BTMinerM20SPlusV30 from .M20S_Plus import BTMinerM20SPlusV30
from .M21 import BTMinerM21V10 from .M21 import BTMinerM21V10

View File

@@ -24,7 +24,7 @@ from pyasic.errors import APIWarning
class BaseWebAPI(ABC): class BaseWebAPI(ABC):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
# ip address of the miner # ip address of the miner
self.ip = ip # ipaddress.ip_address(ip) self.ip = ip # ipaddress.ip_address(ip)
self.username = "root" self.username = "root"
self.pwd = "root" self.pwd = "root"

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "pyasic" name = "pyasic"
version = "0.38.4" version = "0.38.10"
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH." description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
authors = ["UpstreamData <brett@upstreamdata.ca>"] authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic" repository = "https://github.com/UpstreamData/pyasic"