Compare commits

...

11 Commits

15 changed files with 138 additions and 74 deletions

View File

@@ -46,7 +46,7 @@ from pyasic.network import MinerNetwork
async def scan_and_get_data():
# Define network range to be used for scanning
# This can take a list of IPs, a constructor string, or an IP and subnet mask
# The standard mask is /24, and you can pass any IP address in the subnet
# The standard mask is /24 (x.x.x.0-255), and you can pass any IP address in the subnet
net = MinerNetwork("192.168.1.69", mask=24)
# Scan the network for miners
# This function returns a list of miners of the correct type as a class
@@ -93,6 +93,10 @@ if __name__ == "__main__":
If needed, this library exposes a wrapper for the miner API that can be used for advanced data gathering.
You can see more information on basic usage of the APIs past this example in the docs [here](https://pyasic.readthedocs.io/en/latest/API/api/).
Please see the appropriate API documentation page (pyasic docs -> Advanced -> Miner APIs -> your API type) for a link to that specific miner's API documentation page and more information.
#### List available API commands
```python
import asyncio
@@ -105,7 +109,8 @@ async def get_api_commands(miner_ip: str):
miner = await get_miner(miner_ip)
# List all available commands
print(miner.api.get_commands())
# Can also be called explicitly with the function miner.api.get_commands()
print(miner.api.commands)
if __name__ == "__main__":

View File

@@ -90,6 +90,9 @@ details {
</details>
<details>
<summary><a href="../whatsminer/M3X/#m31s">M31S</a></summary>
<summary><a href="../whatsminer/M3X/#m31sv10">M31SV10</a></summary>
<summary><a href="../whatsminer/M3X/#m31sv20">M31SV20</a></summary>
<summary><a href="../whatsminer/M3X/#m31sv60">M31SV60</a></summary>
<summary><a href="../whatsminer/M3X/#m31sv70">M31SV70</a></summary>
</details>
<details>

View File

@@ -114,9 +114,33 @@
show_root_heading: false
heading_level: 4
## M31SV10
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV10
handler: python
options:
show_root_heading: false
heading_level: 4
## M31SV20
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV20
handler: python
options:
show_root_heading: false
heading_level: 4
## M31SV60
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV60
handler: python
options:
show_root_heading: false
heading_level: 4
## M31SV70
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31S
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV70
handler: python
options:
show_root_heading: false

View File

@@ -121,6 +121,7 @@ class BaseMinerAPI:
for func in
# each function in self
dir(self)
if not func == "commands"
if callable(getattr(self, func)) and
# no __ or _ methods
not func.startswith("__") and not func.startswith("_") and

View File

@@ -198,6 +198,8 @@ class BTMinerAPI(BaseMinerAPI):
enc_command = create_privileged_cmd(token_data, command)
data = await self._send_bytes(enc_command)
if not data:
raise APIError("No data was returned from the API.")
data = self._load_api_data(data)
try:

View File

@@ -28,7 +28,7 @@ from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner
from pyasic.settings import PyasicSettings
#TODO: Fix quota splitting in get data
class BOSMiner(BaseMiner):
def __init__(self, ip: str) -> None:
super().__init__(ip)

View File

@@ -238,16 +238,24 @@ class BTMiner(BaseMiner):
async def get_config(self) -> MinerConfig:
pools = None
summary = None
cfg = MinerConfig()
try:
pools = await self.api.pools()
data = await self.api.multicommand("pools", "summary")
pools = data["pools"][0]
summary = data["summary"][0]
except APIError as e:
logging.warning(e)
if pools:
if "POOLS" in pools.keys():
if "POOLS" in pools:
cfg = cfg.from_api(pools["POOLS"])
if summary:
if "SUMMARY" in summary:
if wattage := summary["SUMMARY"][0].get("Power Limit"):
cfg.autotuning_wattage = wattage
return cfg
async def get_data(self, allow_warning: bool = True) -> MinerData:

View File

@@ -24,6 +24,31 @@ class M31S(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M31SV10(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M31S V10"
self.nominal_chips = 105
self.fan_count = 2
class M31SV20(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M31S V20"
self.nominal_chips = 111
self.fan_count = 2
class M31SV60(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M31S V60"
self.nominal_chips = 105
self.fan_count = 2
class M31SV70(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()

View File

@@ -20,7 +20,7 @@ from .M30S_Plus_Plus import (
M30SPlusPlusVG40,
M30SPlusPlusVH60,
)
from .M31S import M31S, M31SV70
from .M31S import M31S, M31SV10, M31SV20, M31SV60, M31SV70
from .M31S_Plus import (
M31SPlus,
M31SPlusV30,

View File

@@ -218,6 +218,9 @@ class BaseMiner(ABC):
async def set_power_limit(self, wattage: int) -> bool:
"""Set the power limit to be used by the miner.
Parameters:
wattage: The power limit to set on the miner.
Returns:
A boolean value of the success of setting the power limit.
"""

View File

@@ -195,7 +195,10 @@ MINER_CLASSES = {
"M31S": {
"Default": BTMinerM31S,
"BTMiner": BTMinerM31S,
"V70": BTMinerM31SV70,
"10": BTMinerM31SV10,
"20": BTMinerM31SV20,
"60": BTMinerM31SV60,
"70": BTMinerM31SV70,
},
"M31S+": {
"Default": BTMinerM31SPlus,

View File

@@ -13,7 +13,7 @@
# limitations under the License.
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
from pyasic.miners._types import M31S, M31SV70 # noqa - Ignore access to _module
from pyasic.miners._types import M31S, M31SV10, M31SV20, M31SV60, M31SV70 # noqa - Ignore access to _module
class BTMinerM31S(BTMiner, M31S):
@@ -21,6 +21,23 @@ class BTMinerM31S(BTMiner, M31S):
super().__init__(ip)
self.ip = ip
class BTMinerM31SV20(BTMiner, M31SV20):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM31SV10(BTMiner, M31SV10):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM31SV60(BTMiner, M31SV60):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM31SV70(BTMiner, M31SV70):
def __init__(self, ip: str) -> None:

View File

@@ -31,7 +31,7 @@ from .M30S_Plus_Plus import (
BTMinerM30SPlusPlusVG40,
BTMinerM30SPlusPlusVH60,
)
from .M31S import BTMinerM31S, BTMinerM31SV70
from .M31S import BTMinerM31S, BTMinerM31SV10, BTMinerM31SV20, BTMinerM31SV60, BTMinerM31SV70
from .M31S_Plus import (
BTMinerM31SPlus,
BTMinerM31SPlusV30,

View File

@@ -110,23 +110,9 @@ class MinerNetwork:
scan_tasks = []
miners = []
# for each IP in the network
for host in local_network.hosts():
# make sure we don't exceed the allowed async tasks
if len(scan_tasks) < round(PyasicSettings().network_scan_threads):
# add the task to the list
scan_tasks.append(self.ping_and_get_miner(host))
else:
# run the scan tasks
miners_scan = await asyncio.gather(*scan_tasks)
# add scanned miners to the list of found miners
miners.extend(miners_scan)
# empty the task list
scan_tasks = []
# do a final scan to empty out the list
miners_scan = await asyncio.gather(*scan_tasks)
miners.extend(miners_scan)
limit = asyncio.Semaphore(PyasicSettings().network_scan_threads)
miners = await asyncio.gather(*[self.ping_and_get_miner(host, limit) for host in local_network.hosts()])
# remove all None from the miner list
miners = list(filter(None, miners))
@@ -151,60 +137,47 @@ class MinerNetwork:
# create a list of scan tasks
scan_tasks = []
# for each ip on the network, loop through and scan it
for host in local_network.hosts():
# make sure we don't exceed the allowed async tasks
if len(scan_tasks) >= round(PyasicSettings().network_scan_threads):
# scanned is a loopable list of awaitables
scanned = asyncio.as_completed(scan_tasks)
# when we scan, empty the scan tasks
scan_tasks = []
# yield miners as they are scanned
for miner in scanned:
yield await miner
# add the ping to the list of tasks if we dont scan
scan_tasks.append(loop.create_task(self.ping_and_get_miner(host)))
# do one last scan at the end to close out the list
scanned = asyncio.as_completed(scan_tasks)
for miner in scanned:
limit = asyncio.Semaphore(PyasicSettings().network_scan_threads)
miners = asyncio.as_completed([loop.create_task(self.ping_and_get_miner(host, limit)) for host in local_network.hosts()])
for miner in miners:
yield await miner
@staticmethod
async def ping_miner(ip: ipaddress.ip_address) -> Union[None, ipaddress.ip_address]:
try:
miner = await ping_miner(ip)
if miner:
return miner
except ConnectionRefusedError:
tasks = [ping_miner(ip, port=port) for port in [4029, 8889]]
for miner in asyncio.as_completed(tasks):
try:
miner = await miner
if miner:
return miner
except ConnectionRefusedError:
pass
async def ping_miner(ip: ipaddress.ip_address, semaphore: asyncio.Semaphore) -> Union[None, ipaddress.ip_address]:
async with semaphore:
try:
miner = await ping_miner(ip)
if miner:
return miner
except ConnectionRefusedError:
tasks = [ping_miner(ip, port=port) for port in [4029, 8889]]
for miner in asyncio.as_completed(tasks):
try:
miner = await miner
if miner:
return miner
except ConnectionRefusedError:
pass
@staticmethod
async def ping_and_get_miner(
ip: ipaddress.ip_address,
ip: ipaddress.ip_address, semaphore: asyncio.Semaphore
) -> Union[None, AnyMiner]:
try:
miner = await ping_and_get_miner(ip)
if miner:
return miner
except ConnectionRefusedError:
tasks = [ping_and_get_miner(ip, port=port) for port in [4029, 8889]]
for miner in asyncio.as_completed(tasks):
try:
miner = await miner
if miner:
return miner
except ConnectionRefusedError:
pass
async with semaphore:
try:
miner = await ping_and_get_miner(ip)
if miner:
return miner
except ConnectionRefusedError:
tasks = [ping_and_get_miner(ip, port=port) for port in [4029, 8889]]
for miner in asyncio.as_completed(tasks):
try:
miner = await miner
if miner:
return miner
except ConnectionRefusedError:
pass
async def ping_miner(

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyasic"
version = "0.23.1"
version = "0.24.1"
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>"]
repository = "https://github.com/UpstreamData/pyasic"