Compare commits
9 Commits
dynamic-wm
...
v0.76.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0fdec3ce5 | ||
|
|
1a8928de18 | ||
|
|
bce0058930 | ||
|
|
850656fce4 | ||
|
|
8bb35d6d7c | ||
|
|
e85f06dbc2 | ||
|
|
a566801316 | ||
|
|
e1a9cc5d19 | ||
|
|
27bb06de2b |
@@ -53,6 +53,7 @@ details {
|
||||
<li><a href="../antminer/X9#s9j-stock">S9j (Stock)</a></li>
|
||||
<li><a href="../antminer/X9#t9-stock">T9 (Stock)</a></li>
|
||||
<li><a href="../antminer/X9#l9-stock">L9 (Stock)</a></li>
|
||||
<li><a href="../antminer/X9#l9-stock">L9 (Stock)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
|
||||
@@ -783,67 +783,67 @@ class BTMinerV2(StockFirmware):
|
||||
BTMINERV3_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac", [RPCAPICommand("rpc_get_device_info", "get_device_info")]
|
||||
"_get_mac", [RPCAPICommand("rpc_get_device_info", "get.device.info")]
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_version",
|
||||
[RPCAPICommand("rpc_get_device_info", "get_device_info")],
|
||||
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_firmware_version",
|
||||
[RPCAPICommand("rpc_get_device_info", "get_device_info")],
|
||||
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname", [RPCAPICommand("rpc_get_device_info", "get_device_info")]
|
||||
"_get_hostname", [RPCAPICommand("rpc_get_device_info", "get.device.info")]
|
||||
),
|
||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||
"_get_light_flashing",
|
||||
[RPCAPICommand("rpc_get_device_info", "get_device_info")],
|
||||
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
"_get_wattage_limit",
|
||||
[RPCAPICommand("rpc_get_device_info", "get_device_info")],
|
||||
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||
),
|
||||
str(DataOptions.FAN_PSU): DataFunction(
|
||||
"_get_psu_fans", [RPCAPICommand("rpc_get_device_info", "get_device_info")]
|
||||
"_get_psu_fans", [RPCAPICommand("rpc_get_device_info", "get.device.info")]
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[
|
||||
RPCAPICommand("rpc_get_device_info", "get_device_info"),
|
||||
RPCAPICommand("rpc_get_device_info", "get.device.info"),
|
||||
RPCAPICommand(
|
||||
"rpc_get_miner_status_edevs",
|
||||
"get_miner_status_edevs",
|
||||
"get.miner.status:edevs",
|
||||
),
|
||||
],
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools",
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||
"_get_env_temp",
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
||||
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -1082,7 +1082,9 @@ class BTMinerV3(StockFirmware):
|
||||
temp=board_data.get("chip-temp-min"),
|
||||
inlet_temp=board_data.get("chip-temp-min"),
|
||||
outlet_temp=board_data.get("chip-temp-max"),
|
||||
serial_number=board_data.get(f"pcbsn{idx}"),
|
||||
serial_number=rpc_get_device_info.get("msg", {})
|
||||
.get("miner", {})
|
||||
.get(f"pcbsn{idx}"),
|
||||
chips=board_data.get("effective-chips"),
|
||||
expected_chips=self.expected_chips,
|
||||
active=(board_data.get("hash-average") or 0) > 0,
|
||||
@@ -1164,12 +1166,16 @@ class BTMinerV3(StockFirmware):
|
||||
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||
except APIError:
|
||||
return None
|
||||
return (
|
||||
res = (
|
||||
rpc_get_miner_status_summary.get("msg", {})
|
||||
.get("summary", {})
|
||||
.get("factory-hash")
|
||||
)
|
||||
|
||||
if res == (-0.001 * self.expected_hashboards):
|
||||
return None
|
||||
return res
|
||||
|
||||
async def _get_env_temp(
|
||||
self, rpc_get_miner_status_summary: dict = None
|
||||
) -> float | None:
|
||||
|
||||
@@ -233,9 +233,10 @@ class ElphapexMiner(StockFirmware):
|
||||
board_temp_data = list(
|
||||
filter(lambda x: not x == 0, board["temp_pcb"])
|
||||
)
|
||||
hashboards[board["index"]].temp = sum(board_temp_data) / len(
|
||||
board_temp_data
|
||||
)
|
||||
if not len(board_temp_data) == 0:
|
||||
hashboards[board["index"]].temp = sum(board_temp_data) / len(
|
||||
board_temp_data
|
||||
)
|
||||
chip_temp_data = list(
|
||||
filter(lambda x: not x == "", board["temp_chip"])
|
||||
)
|
||||
|
||||
@@ -40,5 +40,6 @@ class KS5M(IceRiverMake):
|
||||
raw_model = MinerModel.ICERIVER.KS5M
|
||||
|
||||
expected_fans = 4
|
||||
expected_chips = 18
|
||||
expected_hashboards = 3
|
||||
algo = MinerAlgo.KHEAVYHASH
|
||||
|
||||
@@ -91,6 +91,7 @@ MINER_CLASSES = {
|
||||
"ANTMINER S9J": BMMinerS9j,
|
||||
"ANTMINER T9": BMMinerT9,
|
||||
"ANTMINER L9": BMMinerL9,
|
||||
"ANTMINER L9_I": BMMinerL9,
|
||||
"ANTMINER Z15": CGMinerZ15,
|
||||
"ANTMINER Z15 PRO": BMMinerZ15Pro,
|
||||
"ANTMINER S17": BMMinerS17,
|
||||
|
||||
@@ -23,6 +23,7 @@ import json
|
||||
import logging
|
||||
import re
|
||||
import struct
|
||||
import warnings
|
||||
from asyncio import Future, StreamReader, StreamWriter
|
||||
from typing import Any, AsyncGenerator, Callable, Literal, Union
|
||||
|
||||
@@ -31,7 +32,7 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from passlib.handlers.md5_crypt import md5_crypt
|
||||
|
||||
from pyasic import settings
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.errors import APIError, APIWarning
|
||||
from pyasic.misc import api_min_version, validate_command_output
|
||||
from pyasic.rpc.base import BaseMinerRPCAPI
|
||||
|
||||
@@ -1107,39 +1108,27 @@ class BTMinerV3RPCAPI(BaseMinerRPCAPI):
|
||||
def __init__(self, ip: str, port: int = 4433, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, port, api_ver=api_ver)
|
||||
|
||||
self.reader: StreamReader | None = None
|
||||
self.writer: StreamWriter | None = None
|
||||
self.reader_loop = None
|
||||
|
||||
self.salt = None
|
||||
|
||||
self.cmd_results = {}
|
||||
self.cmd_callbacks = {"get.miner.report": set()}
|
||||
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||
"""Creates and sends multiple commands as one command to the miner.
|
||||
|
||||
async def connect(self):
|
||||
self.reader, self.writer = await asyncio.open_connection(
|
||||
str(self.ip), self.port
|
||||
)
|
||||
self.reader_loop = asyncio.create_task(self._read_loop())
|
||||
Parameters:
|
||||
*commands: The commands to send as a multicommand to the miner.
|
||||
allow_warning: A boolean to supress APIWarnings.
|
||||
|
||||
async def disconnect(self):
|
||||
self.writer.close()
|
||||
await self.writer.wait_closed()
|
||||
self.reader_loop.cancel()
|
||||
"""
|
||||
commands = self._check_commands(*commands)
|
||||
data = await self._send_split_multicommand(*commands)
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
async def send_command(
|
||||
self, command: str, parameters: Any = None, **kwargs
|
||||
) -> dict:
|
||||
if self.writer is None:
|
||||
await self.connect()
|
||||
|
||||
while command in self.cmd_results:
|
||||
wait_fut = self.cmd_results[command]
|
||||
await wait_fut
|
||||
|
||||
result_fut = Future()
|
||||
self.cmd_results[command] = result_fut
|
||||
|
||||
if ":" in command:
|
||||
parameters = command.split(":")[1]
|
||||
command = command.split(":")[0]
|
||||
cmd = {"cmd": command}
|
||||
if parameters is not None:
|
||||
cmd["param"] = parameters
|
||||
@@ -1159,33 +1148,80 @@ class BTMinerV3RPCAPI(BaseMinerRPCAPI):
|
||||
# send the command
|
||||
ser = json.dumps(cmd).encode("utf-8")
|
||||
header = struct.pack("<I", len(ser))
|
||||
await self._send_bytes(header + json.dumps(cmd).encode("utf-8"))
|
||||
return json.loads(
|
||||
await self._send_bytes(header + json.dumps(cmd).encode("utf-8"))
|
||||
)
|
||||
|
||||
await result_fut
|
||||
return result_fut.result()
|
||||
async def _send_bytes(
|
||||
self,
|
||||
data: bytes,
|
||||
*,
|
||||
port: int = None,
|
||||
timeout: int = 100,
|
||||
) -> bytes:
|
||||
if port is None:
|
||||
port = self.port
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Sending")
|
||||
try:
|
||||
# get reader and writer streams
|
||||
reader, writer = await asyncio.open_connection(str(self.ip), port)
|
||||
# handle OSError 121
|
||||
except OSError as e:
|
||||
if e.errno == 121:
|
||||
logging.warning(
|
||||
f"{self} - ([Hidden] Send Bytes) - Semaphore timeout expired."
|
||||
)
|
||||
return b"{}"
|
||||
|
||||
async def _read_loop(self):
|
||||
while True:
|
||||
result = await self._read_bytes()
|
||||
data = self._load_api_data(result)
|
||||
command = data["desc"]
|
||||
if command in self.cmd_callbacks:
|
||||
callbacks: list[Callable] = self.cmd_callbacks[command]
|
||||
await asyncio.gather(*[callback(data) for callback in callbacks])
|
||||
elif command in self.cmd_results:
|
||||
future: Future = self.cmd_results.pop(command)
|
||||
future.set_result(data)
|
||||
# send the command
|
||||
try:
|
||||
data_task = asyncio.create_task(self._read_bytes(reader, timeout=timeout))
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Writing")
|
||||
writer.write(data)
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
||||
await writer.drain()
|
||||
|
||||
await data_task
|
||||
ret_data = data_task.result()
|
||||
except TimeoutError:
|
||||
logging.warning(f"{self} - ([Hidden] Send Bytes) - Read timeout expired.")
|
||||
return b"{}"
|
||||
|
||||
# close the connection
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Closing")
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
|
||||
return ret_data
|
||||
|
||||
def _check_commands(self, *commands) -> list:
|
||||
return_commands = []
|
||||
|
||||
for command in commands:
|
||||
if command.startswith("get.") or command.startswith("set."):
|
||||
return_commands.append(command)
|
||||
else:
|
||||
logging.error(f"Received unexpected data for {self}: {data}")
|
||||
warnings.warn(
|
||||
f"""Removing incorrect command: {command}
|
||||
If you are sure you want to use this command please use API.send_command("{command}", ignore_errors=True) instead.""",
|
||||
APIWarning,
|
||||
)
|
||||
return return_commands
|
||||
|
||||
async def _read_bytes(self, **kwargs) -> bytes:
|
||||
header = await self.reader.readexactly(4)
|
||||
length = struct.unpack("<I", header)[0]
|
||||
return await self.reader.readexactly(length)
|
||||
async def _read_bytes(self, reader: asyncio.StreamReader, timeout: int) -> bytes:
|
||||
ret_data = b""
|
||||
|
||||
async def _send_bytes(self, data: bytes, **kwargs):
|
||||
self.writer.write(data)
|
||||
await self.writer.drain()
|
||||
# loop to receive all the data
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
|
||||
try:
|
||||
header = await reader.readexactly(4)
|
||||
length = struct.unpack("<I", header)[0]
|
||||
ret_data = await reader.readexactly(length)
|
||||
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logging.warning(f"{self} - ([Hidden] Send Bytes) - API Command Error {e}")
|
||||
return ret_data
|
||||
|
||||
async def get_salt(self) -> str:
|
||||
if self.salt is not None:
|
||||
|
||||
@@ -120,18 +120,28 @@ class ElphapexWebAPI(BaseWebAPI):
|
||||
"""
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
|
||||
try:
|
||||
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
|
||||
ret = await client.get(url, auth=auth)
|
||||
except httpx.HTTPError:
|
||||
pass
|
||||
else:
|
||||
if ret.status_code == 200:
|
||||
try:
|
||||
json_data = ret.json()
|
||||
return {command: json_data}
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
async def _send():
|
||||
try:
|
||||
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
|
||||
ret = await client.get(url, auth=auth)
|
||||
except httpx.HTTPError:
|
||||
pass
|
||||
else:
|
||||
if ret.status_code == 200:
|
||||
try:
|
||||
json_data = ret.json()
|
||||
if json_data.get("STATUS", {}).get("STATUS") not in ["S", "I"]:
|
||||
return None
|
||||
return {command: json_data}
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
return None
|
||||
|
||||
# retry 3 times
|
||||
for i in range(3):
|
||||
res = await _send()
|
||||
if res is not None:
|
||||
return res
|
||||
return {command: {}}
|
||||
|
||||
async def get_miner_conf(self) -> dict:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "pyasic"
|
||||
version = "0.75.2"
|
||||
version = "0.76.3"
|
||||
|
||||
description = "A simplified and standardized interface for Bitcoin ASICs."
|
||||
authors = [{name = "UpstreamData", email = "brett@upstreamdata.ca"}]
|
||||
|
||||
Reference in New Issue
Block a user