Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c3b5599fe | ||
|
|
e421eaa324 | ||
|
|
53f3fc5ee9 | ||
|
|
1b36de4131 | ||
|
|
6f0c6f6284 | ||
|
|
b7dda5bf87 | ||
|
|
53a3bbf531 | ||
|
|
50586f1ce7 | ||
|
|
9f6235a0fc | ||
|
|
4d21f150ce | ||
|
|
7c0dfc49dd | ||
|
|
269b13f6c1 | ||
|
|
a9bb7d2e5a | ||
|
|
11295f27a7 | ||
|
|
55aa3dd85b |
@@ -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.*
|
||||||
|
|
||||||
[](https://github.com/psf/black)
|
[](https://github.com/psf/black)
|
||||||
[](https://pypi.org/project/pyasic/)
|
[](https://pypi.org/project/pyasic/)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
35
pyasic/miners/types/whatsminer/M2X/M20P.py
Normal file
35
pyasic/miners/types/whatsminer/M2X/M20P.py
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
26
pyasic/miners/whatsminer/btminer/M2X/M20P.py
Normal file
26
pyasic/miners/whatsminer/btminer/M2X/M20P.py
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user