bug: fix some issues with whatsminer BTMinerV3 implementation
This commit is contained in:
@@ -783,67 +783,67 @@ class BTMinerV2(StockFirmware):
|
|||||||
BTMINERV3_DATA_LOC = DataLocations(
|
BTMINERV3_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction(
|
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(
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
"_get_api_version",
|
"_get_api_version",
|
||||||
[RPCAPICommand("rpc_get_device_info", "get_device_info")],
|
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_firmware_version",
|
"_get_firmware_version",
|
||||||
[RPCAPICommand("rpc_get_device_info", "get_device_info")],
|
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction(
|
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(
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
"_get_light_flashing",
|
"_get_light_flashing",
|
||||||
[RPCAPICommand("rpc_get_device_info", "get_device_info")],
|
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||||
"_get_wattage_limit",
|
"_get_wattage_limit",
|
||||||
[RPCAPICommand("rpc_get_device_info", "get_device_info")],
|
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans",
|
"_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(
|
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(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards",
|
"_get_hashboards",
|
||||||
[
|
[
|
||||||
RPCAPICommand("rpc_get_device_info", "get_device_info"),
|
RPCAPICommand("rpc_get_device_info", "get.device.info"),
|
||||||
RPCAPICommand(
|
RPCAPICommand(
|
||||||
"rpc_get_miner_status_edevs",
|
"rpc_get_miner_status_edevs",
|
||||||
"get_miner_status_edevs",
|
"get.miner.status:edevs",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.POOLS): DataFunction(
|
str(DataOptions.POOLS): DataFunction(
|
||||||
"_get_pools",
|
"_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(
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
"_get_uptime",
|
"_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(
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
"_get_wattage",
|
"_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(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate",
|
"_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(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate",
|
"_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(
|
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||||
"_get_env_temp",
|
"_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"),
|
temp=board_data.get("chip-temp-min"),
|
||||||
inlet_temp=board_data.get("chip-temp-min"),
|
inlet_temp=board_data.get("chip-temp-min"),
|
||||||
outlet_temp=board_data.get("chip-temp-max"),
|
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"),
|
chips=board_data.get("effective-chips"),
|
||||||
expected_chips=self.expected_chips,
|
expected_chips=self.expected_chips,
|
||||||
active=(board_data.get("hash-average") or 0) > 0,
|
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()
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
return None
|
||||||
return (
|
res = (
|
||||||
rpc_get_miner_status_summary.get("msg", {})
|
rpc_get_miner_status_summary.get("msg", {})
|
||||||
.get("summary", {})
|
.get("summary", {})
|
||||||
.get("factory-hash")
|
.get("factory-hash")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if res == (-0.001 * self.expected_hashboards):
|
||||||
|
return None
|
||||||
|
return res
|
||||||
|
|
||||||
async def _get_env_temp(
|
async def _get_env_temp(
|
||||||
self, rpc_get_miner_status_summary: dict = None
|
self, rpc_get_miner_status_summary: dict = None
|
||||||
) -> float | None:
|
) -> float | None:
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import struct
|
import struct
|
||||||
|
import warnings
|
||||||
from asyncio import Future, StreamReader, StreamWriter
|
from asyncio import Future, StreamReader, StreamWriter
|
||||||
from typing import Any, AsyncGenerator, Callable, Literal, Union
|
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 passlib.handlers.md5_crypt import md5_crypt
|
||||||
|
|
||||||
from pyasic import settings
|
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.misc import api_min_version, validate_command_output
|
||||||
from pyasic.rpc.base import BaseMinerRPCAPI
|
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"):
|
def __init__(self, ip: str, port: int = 4433, api_ver: str = "0.0.0"):
|
||||||
super().__init__(ip, port, api_ver=api_ver)
|
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.salt = None
|
||||||
|
|
||||||
self.cmd_results = {}
|
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||||
self.cmd_callbacks = {"get.miner.report": set()}
|
"""Creates and sends multiple commands as one command to the miner.
|
||||||
|
|
||||||
async def connect(self):
|
Parameters:
|
||||||
self.reader, self.writer = await asyncio.open_connection(
|
*commands: The commands to send as a multicommand to the miner.
|
||||||
str(self.ip), self.port
|
allow_warning: A boolean to supress APIWarnings.
|
||||||
)
|
|
||||||
self.reader_loop = asyncio.create_task(self._read_loop())
|
|
||||||
|
|
||||||
async def disconnect(self):
|
"""
|
||||||
self.writer.close()
|
commands = self._check_commands(*commands)
|
||||||
await self.writer.wait_closed()
|
data = await self._send_split_multicommand(*commands)
|
||||||
self.reader_loop.cancel()
|
data["multicommand"] = True
|
||||||
|
return data
|
||||||
|
|
||||||
async def send_command(
|
async def send_command(
|
||||||
self, command: str, parameters: Any = None, **kwargs
|
self, command: str, parameters: Any = None, **kwargs
|
||||||
) -> dict:
|
) -> dict:
|
||||||
if self.writer is None:
|
if ":" in command:
|
||||||
await self.connect()
|
parameters = command.split(":")[1]
|
||||||
|
command = command.split(":")[0]
|
||||||
while command in self.cmd_results:
|
|
||||||
wait_fut = self.cmd_results[command]
|
|
||||||
await wait_fut
|
|
||||||
|
|
||||||
result_fut = Future()
|
|
||||||
self.cmd_results[command] = result_fut
|
|
||||||
|
|
||||||
cmd = {"cmd": command}
|
cmd = {"cmd": command}
|
||||||
if parameters is not None:
|
if parameters is not None:
|
||||||
cmd["param"] = parameters
|
cmd["param"] = parameters
|
||||||
@@ -1159,33 +1148,80 @@ class BTMinerV3RPCAPI(BaseMinerRPCAPI):
|
|||||||
# send the command
|
# send the command
|
||||||
ser = json.dumps(cmd).encode("utf-8")
|
ser = json.dumps(cmd).encode("utf-8")
|
||||||
header = struct.pack("<I", len(ser))
|
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
|
async def _send_bytes(
|
||||||
return result_fut.result()
|
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):
|
# send the command
|
||||||
while True:
|
try:
|
||||||
result = await self._read_bytes()
|
data_task = asyncio.create_task(self._read_bytes(reader, timeout=timeout))
|
||||||
data = self._load_api_data(result)
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Writing")
|
||||||
command = data["desc"]
|
writer.write(data)
|
||||||
if command in self.cmd_callbacks:
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
||||||
callbacks: list[Callable] = self.cmd_callbacks[command]
|
await writer.drain()
|
||||||
await asyncio.gather(*[callback(data) for callback in callbacks])
|
|
||||||
elif command in self.cmd_results:
|
await data_task
|
||||||
future: Future = self.cmd_results.pop(command)
|
ret_data = data_task.result()
|
||||||
future.set_result(data)
|
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:
|
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:
|
async def _read_bytes(self, reader: asyncio.StreamReader, timeout: int) -> bytes:
|
||||||
header = await self.reader.readexactly(4)
|
ret_data = b""
|
||||||
length = struct.unpack("<I", header)[0]
|
|
||||||
return await self.reader.readexactly(length)
|
|
||||||
|
|
||||||
async def _send_bytes(self, data: bytes, **kwargs):
|
# loop to receive all the data
|
||||||
self.writer.write(data)
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
|
||||||
await self.writer.drain()
|
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:
|
async def get_salt(self) -> str:
|
||||||
if self.salt is not None:
|
if self.salt is not None:
|
||||||
|
|||||||
Reference in New Issue
Block a user