Compare commits

..

11 Commits

Author SHA1 Message Date
Upstream Data
0e76d4550b version: bump version number 2025-02-11 15:47:56 -07:00
Upstream Data
2d424025e9 feature: add MAC address to mskminer 2025-02-11 15:47:37 -07:00
Upstream Data
bf4903ce4b refactor: remove some unused imports 2025-02-11 09:38:31 -07:00
Upstream Data
4f7f6bf045 version: bump version number 2025-02-11 09:34:57 -07:00
Upstream Data
824890ec97 feature: add very basic support for MSKminer 2025-02-11 09:34:23 -07:00
Upstream Data
ce9d7ffb0f bug: fix issue with vnish preset parsing on some miners 2025-02-11 09:00:59 -07:00
Upstream Data
183b4934c1 refactor: remote unused import in test 2025-02-06 11:44:15 -07:00
Upstream Data
3d2b260b17 version: bump version number 2025-02-06 11:42:33 -07:00
Upstream Data
f88c1734eb bug: fix some issues with avalon 1566, and add tests 2025-02-06 11:42:17 -07:00
Upstream Data
b897ca8363 version: bump version number 2025-02-06 11:33:08 -07:00
Upstream Data
dba341fdae feature: add support for avalon 1566 2025-02-06 11:32:45 -07:00
30 changed files with 1335 additions and 6 deletions

View File

@@ -62,6 +62,8 @@ def backend_str(backend: MinerTypes) -> str:
return "Stock Firmware Volcminers"
case MinerTypes.ELPHAPEX:
return "Stock Firmware Elphapex Miners"
case MinerTypes.MSKMINER:
return "MSKMiner Firmware Miners"
raise TypeError("Unknown miner backend, cannot generate docs")

View File

@@ -716,6 +716,19 @@
show_root_heading: false
heading_level: 0
## S19 No PIC (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.mskminer.X19.S19.MSKMinerS19NoPIC
handler: python
options:
show_root_heading: false
heading_level: 0
## S19 (LuxOS)
- [x] Shutdowns

View File

@@ -0,0 +1,16 @@
# pyasic
## A15X Models
## Avalon 1566 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.avalonminer.cgminer.A15X.A1566.CGMinerAvalon1566
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -554,6 +554,12 @@ details {
<li><a href="../avalonminer/nano#avalon-nano-3-stock">Avalon Nano 3 (Stock)</a></li>
</ul>
</details>
<details>
<summary>A15X Series:</summary>
<ul>
<li><a href="../avalonminer/A15X#avalon-1566-stock">Avalon 1566 (Stock)</a></li>
</ul>
</details>
</ul>
</details>
<details>
@@ -769,6 +775,17 @@ details {
</ul>
</details>
<details>
<summary>MSKMiner Firmware Miners:</summary>
<ul>
<details>
<summary>X19 Series:</summary>
<ul>
<li><a href="../antminer/X19#s19-no-pic-stock">S19 No PIC (Stock)</a></li>
</ul>
</details>
</ul>
</details>
<details>
<summary>LuxOS Firmware Miners:</summary>
<ul>
<details>

View File

@@ -24,7 +24,14 @@ class MiningPreset(MinerConfigValue):
hashrate = None
else:
power = hr_power_split[0].replace("watt", "").strip()
hashrate = hr_power_split[1].replace("TH", "").replace(" LC", "").strip()
hashrate = (
hr_power_split[1]
.replace("TH", "")
.replace("GH", "")
.replace("MH", "")
.replace(" LC", "")
.strip()
)
tuned = web_preset["status"] == "tuned"
modded_psu = web_preset["modded_psu_required"]
return cls(

View File

@@ -448,6 +448,7 @@ class AvalonminerModels(MinerModelType):
Avalon1166Pro = "Avalon 1166 Pro"
Avalon1126Pro = "Avalon 1126 Pro"
Avalon1246 = "Avalon 1246"
Avalon1566 = "Avalon 1566"
AvalonNano3 = "Avalon Nano 3"
def __str__(self):

View File

@@ -21,4 +21,5 @@ from .epic import *
from .hiveon import *
from .luxos import *
from .marathon import *
from .mskminer import *
from .vnish import *

View File

@@ -0,0 +1,24 @@
# ------------------------------------------------------------------------------
# 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.mskminer import MSKMiner
from pyasic.miners.device.models import (
S19NoPIC,
)
class MSKMinerS19NoPIC(MSKMiner, S19NoPIC):
pass

View File

@@ -0,0 +1,17 @@
# ------------------------------------------------------------------------------
# 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 .S19 import MSKMinerS19NoPIC

View File

@@ -0,0 +1,17 @@
# ------------------------------------------------------------------------------
# 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 .X19 import *

View File

@@ -0,0 +1,22 @@
# ------------------------------------------------------------------------------
# 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 AvalonMiner
from pyasic.miners.device.models import Avalon1566
class CGMinerAvalon1566(AvalonMiner, Avalon1566):
pass

View File

@@ -0,0 +1,17 @@
# ------------------------------------------------------------------------------
# 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 .A1566 import CGMinerAvalon1566

View File

@@ -20,4 +20,5 @@ from .A9X import *
from .A10X import *
from .A11X import *
from .A12X import *
from .A15X import *
from .nano import *

View File

@@ -61,6 +61,10 @@ AVALON_NANO_DATA_LOC = DataLocations(
"_get_wattage_limit",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("rpc_stats", "stats")],

View File

@@ -32,6 +32,7 @@ from .innosilicon import Innosilicon
from .luckyminer import LuckyMiner
from .luxminer import LUXMiner
from .marathon import MaraMiner
from .mskminer import MSKMiner
from .unknown import UnknownMiner
from .vnish import VNish
from .whatsminer import M2X, M3X, M5X, M6X, M7X

View File

@@ -57,6 +57,10 @@ AVALON_DATA_LOC = DataLocations(
"_get_wattage_limit",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("rpc_stats", "stats")],
@@ -289,6 +293,21 @@ class AvalonMiner(CGMiner):
except (IndexError, KeyError, ValueError, TypeError):
pass
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_stats is not None:
try:
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
return int(parsed_stats["WALLPOWER"][0])
except (IndexError, KeyError, ValueError, TypeError):
pass
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
if rpc_stats is None:
try:

View File

@@ -0,0 +1,110 @@
from typing import Optional
from pyasic import APIError
from pyasic.device.algorithm import AlgoHashRate
from pyasic.miners.backends import BMMiner
from pyasic.miners.data import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.web.mskminer import MSKMinerWebAPI
MSKMINER_DATA_LOC = DataLocations(
**{
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver",
[RPCAPICommand("rpc_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver",
[RPCAPICommand("rpc_version", "version")],
),
str(DataOptions.MAC): DataFunction(
"_get_mac",
[WebAPICommand("web_info_v1", "info_v1")],
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_pools", "pools")],
),
}
)
class MSKMiner(BMMiner):
"""Handler for MSKMiner"""
data_locations = MSKMINER_DATA_LOC
web: MSKMinerWebAPI
_web_cls = MSKMinerWebAPI
async def _get_hashrate(self, rpc_stats: dict = None) -> Optional[AlgoHashRate]:
# get hr from API
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_stats is not None:
try:
return self.algo.hashrate(
rate=float(rpc_stats["STATS"][0]["total_rate"]),
unit=self.algo.unit.GH,
).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError):
pass
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_stats is not None:
try:
return rpc_stats["STATS"][0]["total_power"]
except (LookupError, ValueError, TypeError):
pass
async def _get_mac(self, web_info_v1: dict = None) -> Optional[str]:
if web_info_v1 is None:
try:
web_info_v1 = await self.web.info_v1()
except APIError:
pass
if web_info_v1 is not None:
try:
return web_info_v1["network_info"]["result"]["macaddr"].upper()
except (LookupError, ValueError, TypeError):
pass

View File

@@ -0,0 +1,27 @@
# ------------------------------------------------------------------------------
# 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.device.algorithm import MinerAlgo
from pyasic.device.models import MinerModel
from pyasic.miners.device.makes import AvalonMinerMake
class Avalon1566(AvalonMinerMake):
raw_model = MinerModel.AVALONMINER.Avalon1566
expected_chips = 160
expected_fans = 2
expected_hashboards = 3
algo = MinerAlgo.SHA256

View File

@@ -0,0 +1,17 @@
# ------------------------------------------------------------------------------
# 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 .A1566 import Avalon1566

View File

@@ -20,4 +20,5 @@ from .A9X import *
from .A10X import *
from .A11X import *
from .A12X import *
from .A15X import *
from .nano import *

View File

@@ -66,6 +66,7 @@ class MinerTypes(enum.Enum):
VOLCMINER = 15
LUCKYMINER = 16
ELPHAPEX = 17
MSKMINER = 18
MINER_CLASSES = {
@@ -504,6 +505,7 @@ MINER_CLASSES = {
"AVALONMINER 1166PRO": CGMinerAvalon1166Pro,
"AVALONMINER 1246": CGMinerAvalon1246,
"AVALONMINER NANO3": CGMinerAvalonNano3,
"AVALONMINER 15-194": CGMinerAvalon1566,
},
MinerTypes.INNOSILICON: {
None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}),
@@ -600,6 +602,10 @@ MINER_CLASSES = {
"ANTMINER S19": HiveonS19,
"ANTMINER S19X88": HiveonS19NoPIC,
},
MinerTypes.MSKMINER: {
None: MSKMiner,
"S19-88": MSKMinerS19NoPIC,
},
MinerTypes.LUX_OS: {
None: LUXMiner,
"ANTMINER S9": LUXMinerS9,
@@ -869,6 +875,8 @@ class MinerFactory:
return MinerTypes.INNOSILICON
if "Miner UI" in web_text:
return MinerTypes.AURADINE
if "<title>Antminer</title>" in web_text:
return MinerTypes.MSKMINER
async def _get_miner_socket(self, ip: str) -> MinerTypes | None:
commands = ["version", "devdetails"]
@@ -945,6 +953,8 @@ class MinerFactory:
return MinerTypes.HIVEON
if "KAONSU" in upper_data:
return MinerTypes.MARATHON
if "RWGLR" in upper_data:
return MinerTypes.MSKMINER
if "ANTMINER" in upper_data and "DEVDETAILS" not in upper_data:
return MinerTypes.ANTMINER
if (
@@ -1160,9 +1170,9 @@ class MinerFactory:
miner_model = sock_json_data["VERSION"][0]["PROD"].upper()
if "-" in miner_model:
miner_model = miner_model.split("-")[0]
if miner_model in ["AVALONNANO", "AVALON0O"]:
nano_subtype = sock_json_data["VERSION"][0]["MODEL"].upper()
miner_model = f"AVALONMINER {nano_subtype}"
if miner_model in ["AVALONNANO", "AVALON0O", "AVALONMINER 15"]:
subtype = sock_json_data["VERSION"][0]["MODEL"].upper()
miner_model = f"AVALONMINER {subtype}"
return miner_model
except (TypeError, LookupError):
pass
@@ -1410,6 +1420,13 @@ class MinerFactory:
except (TypeError, LookupError):
pass
async def get_miner_model_mskminer(self, ip: str) -> str | None:
sock_json_data = await self.send_api_command(ip, "version")
try:
return sock_json_data["VERSION"][0]["Type"].split(" ")[0]
except LookupError:
pass
miner_factory = MinerFactory()

View File

@@ -41,6 +41,7 @@ _settings = { # defaults
"default_hive_web_password": "root",
"default_iceriver_web_password": "12345678",
"default_elphapex_web_password": "root",
"default_mskminer_web_password": "root",
"default_antminer_ssh_password": "miner",
"default_bosminer_ssh_password": "root",
}

72
pyasic/web/mskminer.py Normal file
View File

@@ -0,0 +1,72 @@
# ------------------------------------------------------------------------------
# Copyright 2024 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 __future__ import annotations
import asyncio
import warnings
from typing import Any
import httpx
from pyasic import settings
from pyasic.errors import APIError
from pyasic.web.base import BaseWebAPI
class MSKMinerWebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.username = "admin"
self.pwd = settings.get("default_mskminer_web_password", "root")
async def multicommand(
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
) -> dict:
tasks = {c: asyncio.create_task(getattr(self, c)()) for c in commands}
await asyncio.gather(*[t for t in tasks.values()])
return {t: tasks[t].result() for t in tasks}
async def send_command(
self,
command: str | bytes,
ignore_errors: bool = False,
allow_warning: bool = True,
privileged: bool = False,
**parameters: Any,
) -> dict:
async with httpx.AsyncClient(transport=settings.transport()) as client:
try:
# auth
await client.post(
f"http://{self.ip}:{self.port}/admin/login",
data={"username": self.username, "password": self.pwd},
)
except httpx.HTTPError:
warnings.warn(f"Could not authenticate with miner web: {self}")
try:
resp = await client.post(
f"http://{self.ip}:{self.port}/api/{command}", params=parameters
)
if not resp.status_code == 200:
if not ignore_errors:
raise APIError(f"Command failed: {command}")
warnings.warn(f"Command failed: {command}")
return resp.json()
except httpx.HTTPError:
raise APIError(f"Command failed: {command}")
async def info_v1(self):
return await self.send_command("info_v1")

View File

@@ -1,6 +1,6 @@
[project]
name = "pyasic"
version = "0.71.6"
version = "0.71.10"
description = "A simplified and standardized interface for Bitcoin ASICs."
authors = [{name = "UpstreamData", email = "brett@upstreamdata.ca"}]

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------
from tests.config_tests import TestConfig
from tests.miners_tests import MinersTest, TestElphapexMiners, TestHammerMiners
from tests.miners_tests import *
from tests.network_tests import NetworkTest
from tests.rpc_tests import *

View File

@@ -1,2 +1,3 @@
from .avalonminer_tests import *
from .elphapex_tests import *
from .hammer_tests import *

View File

@@ -0,0 +1 @@
from .version_24102401_25462b2_9ddf522 import TestAvalonMiners

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
from .version_2_6_0_39 import TestMSKMiners

View File

@@ -0,0 +1,500 @@
"""Tests for MSK miner firmware version 2.6.0.39"""
import unittest
from dataclasses import fields
from unittest.mock import patch
from pyasic import APIError, MinerData
from pyasic.data import Fan, HashBoard
from pyasic.device.algorithm import SHA256Unit
from pyasic.miners.antminer import MSKMinerS19NoPIC
POOLS = [
{
"url": "stratum+tcp://stratum.pool.io:3333",
"user": "pool_username.real_worker",
"pwd": "123",
},
{
"url": "stratum+tcp://stratum.pool.io:3334",
"user": "pool_username.real_worker",
"pwd": "123",
},
{
"url": "stratum+tcp://stratum.pool.io:3335",
"user": "pool_username.real_worker",
"pwd": "123",
},
]
data = {
MSKMinerS19NoPIC: {
"web_info_v1": {
# needs updates with real data
"network_info": {
"result": {
"address": "192.168.1.10",
"macaddr": "12:34:56:78:90:12",
"netmask": "255.255.255.0",
}
}
},
"rpc_version": {
"STATUS": [
{
"STATUS": "S",
"When": 1738856891,
"Code": 22,
"Msg": "BMMiner versions",
"Description": "bmminer 1.0.0",
}
],
"VERSION": [
{
"BMMiner": "4.11.1 rwglr",
"API": "3.1",
"Miner": "0.0.1.3",
"CompileTime": "10 Dec 2024 14:34:31 GMT",
"Type": "S19-88 v.2.6.0.39 ",
}
],
"id": 1,
},
"rpc_stats": {
"STATUS": [
{
"STATUS": "S",
"When": 1738856891,
"Code": 70,
"Msg": "BMMiner stats",
"Description": "bmminer 1.0.0",
}
],
"STATS": [
{
"BMMiner": "4.11.1 rwglr",
"Miner": "0.0.1.3",
"CompileTime": "10 Dec 2024 14:34:31 GMT",
"Type": "S19-88 v.2.6.0.39 ",
},
{
"STATS": 0,
"ID": "BC50",
"Elapsed": 1926,
"Calls": 0,
"Wait": 0.000000,
"Max": 0.000000,
"Min": 99999999.000000,
"GHS 5s": 99989.59,
"GHS av": 99761.40,
"miner_count": 3,
"frequency": "",
"fan_num": 4,
"fan1": 5010,
"fan2": 5160,
"fan3": 5070,
"fan4": 5040,
"fan5": 0,
"fan6": 0,
"fan7": 0,
"fan8": 0,
"temp_num": 3,
"temp1": 45,
"temp2": 45,
"temp3": 47,
"temp4": 0,
"temp5": 0,
"temp6": 0,
"temp7": 0,
"temp8": 0,
"temp9": 0,
"temp10": 0,
"temp11": 0,
"temp12": 0,
"temp13": 0,
"temp14": 0,
"temp15": 0,
"temp16": 0,
"temp2_1": 59,
"temp2_2": 57,
"temp2_3": 58,
"temp2_4": 0,
"temp2_5": 0,
"temp2_6": 0,
"temp2_7": 0,
"temp2_8": 0,
"temp2_9": 0,
"temp2_10": 0,
"temp2_11": 0,
"temp2_12": 0,
"temp2_13": 0,
"temp2_14": 0,
"temp2_15": 0,
"temp2_16": 0,
"temp3_1": 59,
"temp3_2": 56,
"temp3_3": 57,
"temp3_4": 0,
"temp3_5": 0,
"temp3_6": 0,
"temp3_7": 0,
"temp3_8": 0,
"temp3_9": 0,
"temp3_10": 0,
"temp3_11": 0,
"temp3_12": 0,
"temp3_13": 0,
"temp3_14": 0,
"temp3_15": 0,
"temp3_16": 0,
"temp_pcb1": "45-42-45-42",
"temp_pcb2": "45-42-45-42",
"temp_pcb3": "47-43-47-43",
"temp_pcb4": "0-0-0-0",
"temp_pcb5": "0-0-0-0",
"temp_pcb6": "0-0-0-0",
"temp_pcb7": "0-0-0-0",
"temp_pcb8": "0-0-0-0",
"temp_pcb9": "0-0-0-0",
"temp_pcb10": "0-0-0-0",
"temp_pcb11": "0-0-0-0",
"temp_pcb12": "0-0-0-0",
"temp_pcb13": "0-0-0-0",
"temp_pcb14": "0-0-0-0",
"temp_pcb15": "0-0-0-0",
"temp_pcb16": "0-0-0-0",
"temp_chip1": "59-59-59-59",
"temp_chip2": "57-56-57-56",
"temp_chip3": "58-57-58-57",
"temp_chip4": "0-0-0-0",
"temp_chip5": "0-0-0-0",
"temp_chip6": "0-0-0-0",
"temp_chip7": "0-0-0-0",
"temp_chip8": "0-0-0-0",
"temp_chip9": "0-0-0-0",
"temp_chip10": "0-0-0-0",
"temp_chip11": "0-0-0-0",
"temp_chip12": "0-0-0-0",
"temp_chip13": "0-0-0-0",
"temp_chip14": "0-0-0-0",
"temp_chip15": "0-0-0-0",
"temp_chip16": "0-0-0-0",
"total_rateideal": 99674.88,
"total_freqavg": 0.00,
"total_acn": 264,
"total_rate": 99989.59,
"chain_rateideal1": 33677.28,
"chain_rateideal2": 32788.06,
"chain_rateideal3": 33209.54,
"chain_rateideal4": 31436.24,
"chain_rateideal5": 31436.24,
"chain_rateideal6": 31436.24,
"chain_rateideal7": 31436.24,
"chain_rateideal8": 31436.24,
"chain_rateideal9": 31436.24,
"chain_rateideal10": 31436.24,
"chain_rateideal11": 31436.24,
"chain_rateideal12": 31436.24,
"chain_rateideal13": 31436.24,
"chain_rateideal14": 31436.24,
"chain_rateideal15": 31436.24,
"chain_rateideal16": 31436.24,
"temp_max": 47,
"no_matching_work": 0,
"chain_acn1": 88,
"chain_acn2": 88,
"chain_acn3": 88,
"chain_acn4": 0,
"chain_acn5": 0,
"chain_acn6": 0,
"chain_acn7": 0,
"chain_acn8": 0,
"chain_acn9": 0,
"chain_acn10": 0,
"chain_acn11": 0,
"chain_acn12": 0,
"chain_acn13": 0,
"chain_acn14": 0,
"chain_acn15": 0,
"chain_acn16": 0,
"chain_acs1": " oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo",
"chain_acs2": " oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo",
"chain_acs3": " oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo",
"chain_acs4": "",
"chain_acs5": "",
"chain_acs6": "",
"chain_acs7": "",
"chain_acs8": "",
"chain_acs9": "",
"chain_acs10": "",
"chain_acs11": "",
"chain_acs12": "",
"chain_acs13": "",
"chain_acs14": "",
"chain_acs15": "",
"chain_acs16": "",
"chain_hw1": 0,
"chain_hw2": 0,
"chain_hw3": 0,
"chain_hw4": 0,
"chain_hw5": 0,
"chain_hw6": 0,
"chain_hw7": 0,
"chain_hw8": 0,
"chain_hw9": 0,
"chain_hw10": 0,
"chain_hw11": 0,
"chain_hw12": 0,
"chain_hw13": 0,
"chain_hw14": 0,
"chain_hw15": 0,
"chain_hw16": 0,
"chain_rate1": 34084.86,
"chain_rate2": 32303.65,
"chain_rate3": 33601.08,
"chain_rate4": 0.00,
"chain_rate5": 0.00,
"chain_rate6": 0.00,
"chain_rate7": 0.00,
"chain_rate8": 0.00,
"chain_rate9": 0.00,
"chain_rate10": 0.00,
"chain_rate11": 0.00,
"chain_rate12": 0.00,
"chain_rate13": 0.00,
"chain_rate14": 0.00,
"chain_rate15": 0.00,
"chain_rate16": 0.00,
"chain_xtime1": "{}",
"chain_xtime2": "{}",
"chain_xtime3": "{}",
"chain_offside_1": "",
"chain_offside_2": "",
"chain_offside_3": "",
"chain_opencore_0": "1",
"chain_opencore_1": "1",
"chain_opencore_2": "1",
"freq1": 744,
"freq2": 724,
"freq3": 734,
"freq4": 0,
"freq5": 0,
"freq6": 0,
"freq7": 0,
"freq8": 0,
"freq9": 0,
"freq10": 0,
"freq11": 0,
"freq12": 0,
"freq13": 0,
"freq14": 0,
"freq15": 0,
"freq16": 0,
"chain_avgrate1": 33585.34,
"chain_avgrate2": 32788.97,
"chain_avgrate3": 33336.44,
"chain_avgrate4": 0.00,
"chain_avgrate5": 0.00,
"chain_avgrate6": 0.00,
"chain_avgrate7": 0.00,
"chain_avgrate8": 0.00,
"chain_avgrate9": 0.00,
"chain_avgrate10": 0.00,
"chain_avgrate11": 0.00,
"chain_avgrate12": 0.00,
"chain_avgrate13": 0.00,
"chain_avgrate14": 0.00,
"chain_avgrate15": 0.00,
"chain_avgrate16": 0.00,
"miner_version": "0.0.1.3",
"miner_id": "",
"chain_power1": 1135,
"chain_power2": 1103,
"chain_power3": 1118,
"total_power": 3358,
"chain_voltage1": 15.70,
"chain_voltage2": 15.70,
"chain_voltage3": 15.70,
"chain_voltage4": 15.70,
"chain_voltage5": 15.70,
"chain_voltage6": 15.70,
"chain_voltage7": 15.70,
"chain_voltage8": 15.70,
"chain_voltage9": 15.70,
"chain_voltage10": 15.70,
"chain_voltage11": 15.70,
"chain_voltage12": 15.70,
"chain_voltage13": 15.70,
"chain_voltage14": 15.70,
"chain_voltage15": 15.70,
"chain_voltage16": 15.70,
"fan_pwm": 82,
"bringup_temp": 16,
"has_pic": "1",
"tune_running": "0",
"psu_status": "PSU OK",
"downscale_mode": "0",
"has_hotel_fee": "0",
},
],
"id": 1,
},
"rpc_pools": {
"STATUS": [
{
"Code": 7,
"Description": "cgminer 1.0.0",
"Msg": "3 Pool(s)",
"STATUS": "S",
"When": 1732121693,
}
],
"POOLS": [
{
"Accepted": 10000,
"Best Share": 1000000000.0,
"Diff": "100K",
"Diff1 Shares": 0,
"Difficulty Accepted": 1000000000.0,
"Difficulty Rejected": 1000000.0,
"Difficulty Stale": 0.0,
"Discarded": 100000,
"Get Failures": 3,
"Getworks": 9000,
"Has GBT": False,
"Has Stratum": True,
"Last Share Difficulty": 100000.0,
"Last Share Time": "0:00:02",
"Long Poll": "N",
"POOL": 0,
"Pool Rejected%": 0.0,
"Pool Stale%%": 0.0,
"Priority": 0,
"Proxy": "",
"Proxy Type": "",
"Quota": 1,
"Rejected": 100,
"Remote Failures": 0,
"Stale": 0,
"Status": "Alive",
"Stratum Active": True,
"Stratum URL": "stratum.pool.io",
"URL": "stratum+tcp://stratum.pool.io:3333",
"User": "pool_username.real_worker",
},
{
"Accepted": 10000,
"Best Share": 1000000000.0,
"Diff": "100K",
"Diff1 Shares": 0,
"Difficulty Accepted": 1000000000.0,
"Difficulty Rejected": 1000000.0,
"Difficulty Stale": 0.0,
"Discarded": 100000,
"Get Failures": 3,
"Getworks": 9000,
"Has GBT": False,
"Has Stratum": True,
"Last Share Difficulty": 100000.0,
"Last Share Time": "0:00:02",
"Long Poll": "N",
"POOL": 1,
"Pool Rejected%": 0.0,
"Pool Stale%%": 0.0,
"Priority": 0,
"Proxy": "",
"Proxy Type": "",
"Quota": 1,
"Rejected": 100,
"Remote Failures": 0,
"Stale": 0,
"Status": "Alive",
"Stratum Active": True,
"Stratum URL": "stratum.pool.io",
"URL": "stratum+tcp://stratum.pool.io:3333",
"User": "pool_username.real_worker",
},
{
"Accepted": 10000,
"Best Share": 1000000000.0,
"Diff": "100K",
"Diff1 Shares": 0,
"Difficulty Accepted": 1000000000.0,
"Difficulty Rejected": 1000000.0,
"Difficulty Stale": 0.0,
"Discarded": 100000,
"Get Failures": 3,
"Getworks": 9000,
"Has GBT": False,
"Has Stratum": True,
"Last Share Difficulty": 100000.0,
"Last Share Time": "0:00:02",
"Long Poll": "N",
"POOL": 2,
"Pool Rejected%": 0.0,
"Pool Stale%%": 0.0,
"Priority": 0,
"Proxy": "",
"Proxy Type": "",
"Quota": 1,
"Rejected": 100,
"Remote Failures": 0,
"Stale": 0,
"Status": "Alive",
"Stratum Active": True,
"Stratum URL": "stratum.pool.io",
"URL": "stratum+tcp://stratum.pool.io:3333",
"User": "pool_username.real_worker",
},
],
"id": 1,
},
}
}
class TestMSKMiners(unittest.IsolatedAsyncioTestCase):
@patch("pyasic.rpc.base.BaseMinerRPCAPI._send_bytes")
async def test_all_data_gathering(self, mock_send_bytes):
mock_send_bytes.raises = APIError()
for m_type in data:
gathered_data = {}
miner = m_type("127.0.0.1")
for data_name in fields(miner.data_locations):
if data_name.name == "config":
# skip
continue
data_func = getattr(miner.data_locations, data_name.name)
fn_args = data_func.kwargs
args_to_send = {k.name: data[m_type][k.name] for k in fn_args}
function = getattr(miner, data_func.cmd)
gathered_data[data_name.name] = await function(**args_to_send)
result = MinerData(
ip=str(miner.ip),
device_info=miner.device_info,
expected_chips=(
miner.expected_chips * miner.expected_hashboards
if miner.expected_chips is not None
else 0
),
expected_hashboards=miner.expected_hashboards,
expected_fans=miner.expected_fans,
hashboards=[
HashBoard(slot=i, expected_chips=miner.expected_chips)
for i in range(miner.expected_hashboards)
],
)
for item in gathered_data:
if gathered_data[item] is not None:
setattr(result, item, gathered_data[item])
self.assertEqual(result.mac, "12:34:56:78:90:12")
self.assertEqual(result.api_ver, "3.1")
self.assertEqual(result.fw_ver, "10 Dec 2024 14:34:31 GMT")
self.assertEqual(round(result.hashrate.into(SHA256Unit.TH)), 100)
self.assertEqual(
result.fans,
[Fan(speed=5010), Fan(speed=5160), Fan(speed=5070), Fan(speed=5040)],
)
self.assertEqual(result.total_chips, result.expected_chips)