feature: add support for Elphapex DG1+
This commit is contained in:
committed by
Brett Rowan
parent
90d8a795e6
commit
a5c42c9c2b
@@ -60,6 +60,8 @@ def backend_str(backend: MinerTypes) -> str:
|
|||||||
return "Stock Firmware Hammer Miners"
|
return "Stock Firmware Hammer Miners"
|
||||||
case MinerTypes.VOLCMINER:
|
case MinerTypes.VOLCMINER:
|
||||||
return "Stock Firmware Volcminers"
|
return "Stock Firmware Volcminers"
|
||||||
|
case MinerTypes.ELPHAPEX:
|
||||||
|
return "Stock Firmware Elphapex Miners"
|
||||||
raise TypeError("Unknown miner backend, cannot generate docs")
|
raise TypeError("Unknown miner backend, cannot generate docs")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
16
docs/miners/elphapex/DGX.md
Normal file
16
docs/miners/elphapex/DGX.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# pyasic
|
||||||
|
## DGX Models
|
||||||
|
|
||||||
|
## DG1+ (Stock)
|
||||||
|
|
||||||
|
- [ ] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [ ] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.elphapex.daoge.DGX.DG1.ElphapexDG1Plus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
@@ -914,4 +914,15 @@ details {
|
|||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>Stock Firmware Elphapex Miners:</summary>
|
||||||
|
<ul>
|
||||||
|
<details>
|
||||||
|
<summary>DGX Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../elphapex/DGX#dg1_1-stock">DG1+ (Stock)</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -30,6 +30,7 @@ class MinerMake(str, Enum):
|
|||||||
ICERIVER = "IceRiver"
|
ICERIVER = "IceRiver"
|
||||||
HAMMER = "Hammer"
|
HAMMER = "Hammer"
|
||||||
VOLCMINER = "VolcMiner"
|
VOLCMINER = "VolcMiner"
|
||||||
|
ELPHAPEX = "Elphapex"
|
||||||
BRAIINS = "Braiins"
|
BRAIINS = "Braiins"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -550,6 +550,10 @@ class BraiinsModels(MinerModelType):
|
|||||||
BMM101 = "BMM101"
|
BMM101 = "BMM101"
|
||||||
|
|
||||||
|
|
||||||
|
class ElphapexModels(MinerModelType):
|
||||||
|
DG1Plus = "DG1+"
|
||||||
|
|
||||||
|
|
||||||
class MinerModel:
|
class MinerModel:
|
||||||
ANTMINER = AntminerModels
|
ANTMINER = AntminerModels
|
||||||
WHATSMINER = WhatsminerModels
|
WHATSMINER = WhatsminerModels
|
||||||
@@ -563,4 +567,5 @@ class MinerModel:
|
|||||||
ICERIVER = IceRiverModels
|
ICERIVER = IceRiverModels
|
||||||
HAMMER = HammerModels
|
HAMMER = HammerModels
|
||||||
VOLCMINER = VolcMinerModels
|
VOLCMINER = VolcMinerModels
|
||||||
|
ELPHAPEX = ElphapexModels
|
||||||
BRAIINS = BraiinsModels
|
BRAIINS = BraiinsModels
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from .bmminer import BMMiner
|
|||||||
from .braiins_os import BOSer, BOSMiner
|
from .braiins_os import BOSer, BOSMiner
|
||||||
from .btminer import BTMiner
|
from .btminer import BTMiner
|
||||||
from .cgminer import CGMiner
|
from .cgminer import CGMiner
|
||||||
|
from .elphapex import ElphapexMiner
|
||||||
from .epic import ePIC
|
from .epic import ePIC
|
||||||
from .goldshell import GoldshellMiner
|
from .goldshell import GoldshellMiner
|
||||||
from .hammer import BlackMiner
|
from .hammer import BlackMiner
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ ANTMINER_MODERN_DATA_LOC = DataLocations(
|
|||||||
"_get_fault_light",
|
"_get_fault_light",
|
||||||
[WebAPICommand("web_get_blink_status", "get_blink_status")],
|
[WebAPICommand("web_get_blink_status", "get_blink_status")],
|
||||||
),
|
),
|
||||||
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
|
"_get_hashboards",
|
||||||
|
[],
|
||||||
|
),
|
||||||
str(DataOptions.IS_MINING): DataFunction(
|
str(DataOptions.IS_MINING): DataFunction(
|
||||||
"_is_mining",
|
"_is_mining",
|
||||||
[WebAPICommand("web_get_conf", "get_miner_conf")],
|
[WebAPICommand("web_get_conf", "get_miner_conf")],
|
||||||
|
|||||||
319
pyasic/miners/backends/elphapex.py
Normal file
319
pyasic/miners/backends/elphapex.py
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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 typing import List, Optional
|
||||||
|
|
||||||
|
from pyasic import APIError
|
||||||
|
from pyasic.data import Fan, HashBoard, X19Error
|
||||||
|
from pyasic.data.error_codes import MinerErrorData
|
||||||
|
from pyasic.device.algorithm import AlgoHashRate
|
||||||
|
from pyasic.miners.data import (
|
||||||
|
DataFunction,
|
||||||
|
DataLocations,
|
||||||
|
DataOptions,
|
||||||
|
WebAPICommand,
|
||||||
|
)
|
||||||
|
from pyasic.miners.device.firmware import StockFirmware
|
||||||
|
from pyasic.web.elphapex import ElphapexWebAPI
|
||||||
|
|
||||||
|
ELPHAPEX_DATA_LOC = DataLocations(
|
||||||
|
**{
|
||||||
|
str(DataOptions.MAC): DataFunction(
|
||||||
|
"_get_mac",
|
||||||
|
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||||
|
),
|
||||||
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
|
"_get_api_ver",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
|
"_get_fw_ver",
|
||||||
|
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HOSTNAME): DataFunction(
|
||||||
|
"_get_hostname",
|
||||||
|
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
|
"_get_hashboards",
|
||||||
|
[WebAPICommand("web_stats", "stats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
|
"_get_expected_hashrate",
|
||||||
|
[WebAPICommand("web_stats", "stats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FANS): DataFunction(
|
||||||
|
"_get_fans",
|
||||||
|
[WebAPICommand("web_stats", "stats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.ERRORS): DataFunction(
|
||||||
|
"_get_errors",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
|
"_get_fault_light",
|
||||||
|
[WebAPICommand("web_get_blink_status", "get_blink_status")],
|
||||||
|
),
|
||||||
|
str(DataOptions.IS_MINING): DataFunction(
|
||||||
|
"_is_mining",
|
||||||
|
[WebAPICommand("web_get_miner_conf", "get_miner_conf")],
|
||||||
|
),
|
||||||
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
|
"_get_uptime",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ElphapexMiner(StockFirmware):
|
||||||
|
"""Handler for Elphapex miners."""
|
||||||
|
|
||||||
|
_web_cls = ElphapexWebAPI
|
||||||
|
web: ElphapexWebAPI
|
||||||
|
|
||||||
|
data_locations = ELPHAPEX_DATA_LOC
|
||||||
|
|
||||||
|
async def fault_light_on(self) -> bool:
|
||||||
|
data = await self.web.blink(blink=True)
|
||||||
|
if data:
|
||||||
|
if data.get("code") == "B000":
|
||||||
|
self.light = True
|
||||||
|
return self.light
|
||||||
|
|
||||||
|
async def fault_light_off(self) -> bool:
|
||||||
|
data = await self.web.blink(blink=False)
|
||||||
|
if data:
|
||||||
|
if data.get("code") == "B100":
|
||||||
|
self.light = False
|
||||||
|
return self.light
|
||||||
|
|
||||||
|
async def reboot(self) -> bool:
|
||||||
|
data = await self.web.reboot()
|
||||||
|
if data:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _get_api_ver(self, web_summary: dict = None) -> Optional[str]:
|
||||||
|
if web_summary is None:
|
||||||
|
try:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_summary is not None:
|
||||||
|
try:
|
||||||
|
self.api_ver = web_summary["STATUS"]["api_version"]
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return self.api_ver
|
||||||
|
|
||||||
|
async def _get_fw_ver(self, web_get_system_info: dict = None) -> Optional[str]:
|
||||||
|
if web_get_system_info is None:
|
||||||
|
try:
|
||||||
|
web_get_system_info = await self.web.get_system_info()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_get_system_info is not None:
|
||||||
|
try:
|
||||||
|
self.fw_ver = (
|
||||||
|
web_get_system_info["system_filesystem_version"]
|
||||||
|
.upper()
|
||||||
|
.split("V")[-1]
|
||||||
|
)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return self.fw_ver
|
||||||
|
|
||||||
|
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
|
||||||
|
if web_get_system_info is None:
|
||||||
|
try:
|
||||||
|
web_get_system_info = await self.web.get_system_info()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_get_system_info is not None:
|
||||||
|
try:
|
||||||
|
return web_get_system_info["hostname"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_mac(self, web_get_system_info: dict = None) -> Optional[str]:
|
||||||
|
if web_get_system_info is None:
|
||||||
|
try:
|
||||||
|
web_get_system_info = await self.web.get_system_info()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_get_system_info is not None:
|
||||||
|
try:
|
||||||
|
return web_get_system_info["macaddr"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = await self.web.get_network_info()
|
||||||
|
if data:
|
||||||
|
return data["macaddr"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
||||||
|
if web_summary is None:
|
||||||
|
try:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
if web_summary is not None:
|
||||||
|
try:
|
||||||
|
for item in web_summary["SUMMARY"][0]["status"]:
|
||||||
|
try:
|
||||||
|
if not item["status"] == "s":
|
||||||
|
errors.append(X19Error(error_message=item["msg"]))
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
return errors
|
||||||
|
|
||||||
|
async def _get_hashboards(self, web_stats: dict | None = None) -> List[HashBoard]:
|
||||||
|
hashboards = [
|
||||||
|
HashBoard(slot=idx, expected_chips=self.expected_chips)
|
||||||
|
for idx in range(self.expected_hashboards)
|
||||||
|
]
|
||||||
|
|
||||||
|
if web_stats is None:
|
||||||
|
try:
|
||||||
|
web_stats = await self.web.stats()
|
||||||
|
except APIError:
|
||||||
|
return hashboards
|
||||||
|
|
||||||
|
if web_stats is not None:
|
||||||
|
try:
|
||||||
|
for board in web_stats["STATS"][0]["chain"]:
|
||||||
|
hashboards[board["index"]].hashrate = self.algo.hashrate(
|
||||||
|
rate=board["rate_real"], unit=self.algo.unit.MH
|
||||||
|
).into(self.algo.unit.default)
|
||||||
|
hashboards[board["index"]].chips = board["asic_num"]
|
||||||
|
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
|
||||||
|
)
|
||||||
|
chip_temp_data = list(
|
||||||
|
filter(lambda x: not x == "", board["temp_chip"])
|
||||||
|
)
|
||||||
|
hashboards[board["index"]].chip_temp = sum(
|
||||||
|
[int(i) / 1000 for i in chip_temp_data]
|
||||||
|
) / len(chip_temp_data)
|
||||||
|
hashboards[board["index"]].serial_number = board["sn"]
|
||||||
|
hashboards[board["index"]].missing = False
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
return hashboards
|
||||||
|
|
||||||
|
async def _get_fault_light(
|
||||||
|
self, web_get_blink_status: dict = None
|
||||||
|
) -> Optional[bool]:
|
||||||
|
if self.light:
|
||||||
|
return self.light
|
||||||
|
|
||||||
|
if web_get_blink_status is None:
|
||||||
|
try:
|
||||||
|
web_get_blink_status = await self.web.get_blink_status()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_get_blink_status is not None:
|
||||||
|
try:
|
||||||
|
self.light = web_get_blink_status["blink"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return self.light
|
||||||
|
|
||||||
|
async def _get_expected_hashrate(
|
||||||
|
self, web_stats: dict = None
|
||||||
|
) -> Optional[AlgoHashRate]:
|
||||||
|
if web_stats is None:
|
||||||
|
try:
|
||||||
|
web_stats = await self.web.stats()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_stats is not None:
|
||||||
|
try:
|
||||||
|
expected_rate = web_stats["STATS"][1]["total_rateideal"]
|
||||||
|
try:
|
||||||
|
rate_unit = web_stats["STATS"][1]["rate_unit"]
|
||||||
|
except KeyError:
|
||||||
|
rate_unit = "MH"
|
||||||
|
return self.algo.hashrate(
|
||||||
|
rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
|
||||||
|
).into(self.algo.unit.default)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _is_mining(self, web_get_miner_conf: dict = None) -> Optional[bool]:
|
||||||
|
if web_get_miner_conf is None:
|
||||||
|
try:
|
||||||
|
web_get_miner_conf = await self.web.get_miner_conf()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_get_miner_conf is not None:
|
||||||
|
try:
|
||||||
|
if str(web_get_miner_conf["fc-work-mode"]).isdigit():
|
||||||
|
return (
|
||||||
|
False if int(web_get_miner_conf["fc-work-mode"]) == 1 else True
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_uptime(self, web_summary: dict = None) -> Optional[int]:
|
||||||
|
if web_summary is None:
|
||||||
|
try:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_summary is not None:
|
||||||
|
try:
|
||||||
|
return int(web_summary["SUMMARY"][1]["elapsed"])
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_fans(self, web_stats: dict = None) -> List[Fan]:
|
||||||
|
if web_stats is None:
|
||||||
|
try:
|
||||||
|
web_stats = await self.web.stats()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
fans = [Fan() for _ in range(self.expected_fans)]
|
||||||
|
if web_stats is not None:
|
||||||
|
for fan_n in range(self.expected_fans):
|
||||||
|
try:
|
||||||
|
fans[fan_n].speed = int(web_stats["STATS"][0]["fan"][fan_n])
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return fans
|
||||||
@@ -68,3 +68,7 @@ class VolcMinerMake(BaseMiner):
|
|||||||
|
|
||||||
class BraiinsMake(BaseMiner):
|
class BraiinsMake(BaseMiner):
|
||||||
make = MinerMake.BRAIINS
|
make = MinerMake.BRAIINS
|
||||||
|
|
||||||
|
|
||||||
|
class ElphapexMake(BaseMiner):
|
||||||
|
make = MinerMake.ELPHAPEX
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from .antminer import *
|
|||||||
from .auradine import *
|
from .auradine import *
|
||||||
from .avalonminer import *
|
from .avalonminer import *
|
||||||
from .braiins import *
|
from .braiins import *
|
||||||
|
from .elphapex import *
|
||||||
from .epic import *
|
from .epic import *
|
||||||
from .goldshell import *
|
from .goldshell import *
|
||||||
from .hammer import *
|
from .hammer import *
|
||||||
|
|||||||
27
pyasic/miners/device/models/elphapex/DGX/DG1.py
Normal file
27
pyasic/miners/device/models/elphapex/DGX/DG1.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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 pyasic.device.algorithm import MinerAlgo
|
||||||
|
from pyasic.device.models import MinerModel
|
||||||
|
from pyasic.miners.device.makes import ElphapexMake, HammerMake
|
||||||
|
|
||||||
|
|
||||||
|
class DG1Plus(ElphapexMake):
|
||||||
|
raw_model = MinerModel.ELPHAPEX.DG1Plus
|
||||||
|
|
||||||
|
expected_chips = 204
|
||||||
|
expected_hashboards = 4
|
||||||
|
expected_fans = 4
|
||||||
|
algo = MinerAlgo.SCRYPT
|
||||||
1
pyasic/miners/device/models/elphapex/DGX/__init__.py
Normal file
1
pyasic/miners/device/models/elphapex/DGX/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .DG1 import DG1Plus
|
||||||
1
pyasic/miners/device/models/elphapex/__init__.py
Normal file
1
pyasic/miners/device/models/elphapex/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .DGX import *
|
||||||
1
pyasic/miners/elphapex/__init__.py
Normal file
1
pyasic/miners/elphapex/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .daoge import *
|
||||||
6
pyasic/miners/elphapex/daoge/DGX/DG1.py
Normal file
6
pyasic/miners/elphapex/daoge/DGX/DG1.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pyasic.miners.backends.elphapex import ElphapexMiner
|
||||||
|
from pyasic.miners.device.models import DG1Plus
|
||||||
|
|
||||||
|
|
||||||
|
class ElphapexDG1Plus(ElphapexMiner, DG1Plus):
|
||||||
|
pass
|
||||||
1
pyasic/miners/elphapex/daoge/DGX/__init__.py
Normal file
1
pyasic/miners/elphapex/daoge/DGX/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .DG1 import ElphapexDG1Plus
|
||||||
1
pyasic/miners/elphapex/daoge/__init__.py
Normal file
1
pyasic/miners/elphapex/daoge/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .DGX import *
|
||||||
@@ -37,6 +37,7 @@ from pyasic.miners.bitaxe import *
|
|||||||
from pyasic.miners.blockminer import *
|
from pyasic.miners.blockminer import *
|
||||||
from pyasic.miners.braiins import *
|
from pyasic.miners.braiins import *
|
||||||
from pyasic.miners.device.makes import *
|
from pyasic.miners.device.makes import *
|
||||||
|
from pyasic.miners.elphapex import *
|
||||||
from pyasic.miners.goldshell import *
|
from pyasic.miners.goldshell import *
|
||||||
from pyasic.miners.hammer import *
|
from pyasic.miners.hammer import *
|
||||||
from pyasic.miners.iceriver import *
|
from pyasic.miners.iceriver import *
|
||||||
@@ -64,6 +65,7 @@ class MinerTypes(enum.Enum):
|
|||||||
HAMMER = 14
|
HAMMER = 14
|
||||||
VOLCMINER = 15
|
VOLCMINER = 15
|
||||||
LUCKYMINER = 16
|
LUCKYMINER = 16
|
||||||
|
ELPHAPEX = 17
|
||||||
|
|
||||||
|
|
||||||
MINER_CLASSES = {
|
MINER_CLASSES = {
|
||||||
@@ -664,6 +666,10 @@ MINER_CLASSES = {
|
|||||||
None: type("VolcMinerUnknown", (BlackMiner, VolcMinerMake), {}),
|
None: type("VolcMinerUnknown", (BlackMiner, VolcMinerMake), {}),
|
||||||
"VOLCMINER D1": VolcMinerD1,
|
"VOLCMINER D1": VolcMinerD1,
|
||||||
},
|
},
|
||||||
|
MinerTypes.ELPHAPEX: {
|
||||||
|
None: type("ElphapexUnknown", (ElphapexMiner, ElphapexMake), {}),
|
||||||
|
"DG1+1": ElphapexDG1Plus,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -745,6 +751,7 @@ class MinerFactory:
|
|||||||
MinerTypes.ICERIVER: self.get_miner_model_iceriver,
|
MinerTypes.ICERIVER: self.get_miner_model_iceriver,
|
||||||
MinerTypes.HAMMER: self.get_miner_model_hammer,
|
MinerTypes.HAMMER: self.get_miner_model_hammer,
|
||||||
MinerTypes.VOLCMINER: self.get_miner_model_volcminer,
|
MinerTypes.VOLCMINER: self.get_miner_model_volcminer,
|
||||||
|
MinerTypes.ELPHAPEX: self.get_miner_model_elphapex,
|
||||||
}
|
}
|
||||||
fn = miner_model_fns.get(miner_type)
|
fn = miner_model_fns.get(miner_type)
|
||||||
|
|
||||||
@@ -828,6 +835,10 @@ class MinerFactory:
|
|||||||
"www-authenticate", ""
|
"www-authenticate", ""
|
||||||
):
|
):
|
||||||
return MinerTypes.HAMMER
|
return MinerTypes.HAMMER
|
||||||
|
if web_resp.status_code == 401 and 'realm="Daoge' in web_resp.headers.get(
|
||||||
|
"www-authenticate", ""
|
||||||
|
):
|
||||||
|
return MinerTypes.ELPHAPEX
|
||||||
if len(web_resp.history) > 0:
|
if len(web_resp.history) > 0:
|
||||||
history_resp = web_resp.history[0]
|
history_resp = web_resp.history[0]
|
||||||
if (
|
if (
|
||||||
@@ -1384,6 +1395,21 @@ class MinerFactory:
|
|||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def get_miner_model_elphapex(self, ip: str) -> str | None:
|
||||||
|
auth = httpx.DigestAuth(
|
||||||
|
"root", settings.get("default_elphapex_web_password", "root")
|
||||||
|
)
|
||||||
|
web_json_data = await self.send_web_command(
|
||||||
|
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
miner_model = web_json_data["minertype"]
|
||||||
|
|
||||||
|
return miner_model
|
||||||
|
except (TypeError, LookupError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
miner_factory = MinerFactory()
|
miner_factory = MinerFactory()
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ _settings = { # defaults
|
|||||||
"default_epic_web_password": "letmein",
|
"default_epic_web_password": "letmein",
|
||||||
"default_hive_web_password": "root",
|
"default_hive_web_password": "root",
|
||||||
"default_iceriver_web_password": "12345678",
|
"default_iceriver_web_password": "12345678",
|
||||||
|
"default_elphapex_web_password": "root",
|
||||||
"default_antminer_ssh_password": "miner",
|
"default_antminer_ssh_password": "miner",
|
||||||
"default_bosminer_ssh_password": "root",
|
"default_bosminer_ssh_password": "root",
|
||||||
}
|
}
|
||||||
|
|||||||
216
pyasic/web/elphapex.py
Normal file
216
pyasic/web/elphapex.py
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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 __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from pyasic import settings
|
||||||
|
from pyasic.web.base import BaseWebAPI
|
||||||
|
|
||||||
|
|
||||||
|
class ElphapexWebAPI(BaseWebAPI):
|
||||||
|
def __init__(self, ip: str) -> None:
|
||||||
|
"""Initialize the modern Elphapex API client with a specific IP address.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip (str): IP address of the Elphapex device.
|
||||||
|
"""
|
||||||
|
super().__init__(ip)
|
||||||
|
self.username = "root"
|
||||||
|
self.pwd = settings.get("default_elphapex_web_password", "root")
|
||||||
|
|
||||||
|
async def send_command(
|
||||||
|
self,
|
||||||
|
command: str | bytes,
|
||||||
|
ignore_errors: bool = False,
|
||||||
|
allow_warning: bool = True,
|
||||||
|
privileged: bool = False,
|
||||||
|
**parameters: Any,
|
||||||
|
) -> dict:
|
||||||
|
"""Send a command to the Elphapex device using HTTP digest authentication.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command (str | bytes): The CGI command to send.
|
||||||
|
ignore_errors (bool): If True, ignore any HTTP errors.
|
||||||
|
allow_warning (bool): If True, proceed with warnings.
|
||||||
|
privileged (bool): If set to True, requires elevated privileges.
|
||||||
|
**parameters: Arbitrary keyword arguments to be sent as parameters in the request.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The JSON response from the device or an empty dictionary if an error occurs.
|
||||||
|
"""
|
||||||
|
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
|
||||||
|
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||||
|
|
||||||
|
if parameters:
|
||||||
|
data = await client.post(
|
||||||
|
url,
|
||||||
|
auth=auth,
|
||||||
|
timeout=settings.get("api_function_timeout", 3),
|
||||||
|
json=parameters,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
data = await client.get(url, auth=auth)
|
||||||
|
except httpx.HTTPError as e:
|
||||||
|
return {"success": False, "message": f"HTTP error occurred: {str(e)}"}
|
||||||
|
else:
|
||||||
|
if data.status_code == 200:
|
||||||
|
try:
|
||||||
|
return data.json()
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
return {"success": False, "message": "Failed to decode JSON"}
|
||||||
|
return {"success": False, "message": "Unknown error occurred"}
|
||||||
|
|
||||||
|
async def multicommand(
|
||||||
|
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||||
|
) -> dict:
|
||||||
|
"""Execute multiple commands simultaneously.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*commands (str): Multiple command strings to be executed.
|
||||||
|
ignore_errors (bool): If True, ignore any HTTP errors.
|
||||||
|
allow_warning (bool): If True, proceed with warnings.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing the results of all commands executed.
|
||||||
|
"""
|
||||||
|
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||||
|
tasks = [
|
||||||
|
asyncio.create_task(self._handle_multicommand(client, command))
|
||||||
|
for command in commands
|
||||||
|
]
|
||||||
|
all_data = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
for item in all_data:
|
||||||
|
data.update(item)
|
||||||
|
|
||||||
|
data["multicommand"] = True
|
||||||
|
return data
|
||||||
|
|
||||||
|
async def _handle_multicommand(
|
||||||
|
self, client: httpx.AsyncClient, command: str
|
||||||
|
) -> dict:
|
||||||
|
"""Helper function for handling individual commands in a multicommand execution.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client (httpx.AsyncClient): The HTTP client to use for the request.
|
||||||
|
command (str): The command to be executed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing the response of the executed command.
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
return {command: {}}
|
||||||
|
|
||||||
|
async def get_miner_conf(self) -> dict:
|
||||||
|
"""Retrieve the miner configuration from the Elphapex device.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing the current configuration of the miner.
|
||||||
|
"""
|
||||||
|
return await self.send_command("get_miner_conf")
|
||||||
|
|
||||||
|
async def set_miner_conf(self, conf: dict) -> dict:
|
||||||
|
"""Set the configuration for the miner.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
conf (dict): A dictionary of configuration settings to apply to the miner.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary response from the device after setting the configuration.
|
||||||
|
"""
|
||||||
|
return await self.send_command("set_miner_conf", **conf)
|
||||||
|
|
||||||
|
async def blink(self, blink: bool) -> dict:
|
||||||
|
"""Control the blinking of the LED on the miner device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
blink (bool): True to start blinking, False to stop.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary response from the device after the command execution.
|
||||||
|
"""
|
||||||
|
if blink:
|
||||||
|
return await self.send_command("blink", blink="true")
|
||||||
|
return await self.send_command("blink", blink="false")
|
||||||
|
|
||||||
|
async def reboot(self) -> dict:
|
||||||
|
"""Reboot the miner device.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary response from the device confirming the reboot command.
|
||||||
|
"""
|
||||||
|
return await self.send_command("reboot")
|
||||||
|
|
||||||
|
async def get_system_info(self) -> dict:
|
||||||
|
"""Retrieve system information from the miner.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing system information of the miner.
|
||||||
|
"""
|
||||||
|
return await self.send_command("get_system_info")
|
||||||
|
|
||||||
|
async def get_network_info(self) -> dict:
|
||||||
|
"""Retrieve network configuration information from the miner.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing the network configuration of the miner.
|
||||||
|
"""
|
||||||
|
return await self.send_command("get_network_info")
|
||||||
|
|
||||||
|
async def summary(self) -> dict:
|
||||||
|
"""Get a summary of the miner's status and performance.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A summary of the miner's current operational status.
|
||||||
|
"""
|
||||||
|
return await self.send_command("summary")
|
||||||
|
|
||||||
|
async def stats(self) -> dict:
|
||||||
|
"""Get miners stats.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A summary of the miner's current operational status.
|
||||||
|
"""
|
||||||
|
return await self.send_command("stats")
|
||||||
|
|
||||||
|
async def get_blink_status(self) -> dict:
|
||||||
|
"""Check the status of the LED blinking on the miner.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary indicating whether the LED is currently blinking.
|
||||||
|
"""
|
||||||
|
return await self.send_command("get_blink_status")
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from tests.config_tests import TestConfig
|
from tests.config_tests import TestConfig
|
||||||
from tests.miners_tests import MinersTest, TestHammerMiners
|
from tests.miners_tests import MinersTest, TestElphapexMiners, TestHammerMiners
|
||||||
from tests.network_tests import NetworkTest
|
from tests.network_tests import NetworkTest
|
||||||
from tests.rpc_tests import *
|
from tests.rpc_tests import *
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
from .alphapex_tests import *
|
||||||
from .hammer_tests import *
|
from .hammer_tests import *
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from .version_1_0_2 import TestElphapexMiners
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
"""Tests for hammer miners with firmware dating 2023-05-28 17-20-35 CST"""
|
||||||
|
|
||||||
|
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.hashrate.unit.scrypt import ScryptUnit
|
||||||
|
from pyasic.miners.elphapex import ElphapexDG1Plus
|
||||||
|
from pyasic.miners.hammer import HammerD10
|
||||||
|
|
||||||
|
data = {
|
||||||
|
ElphapexDG1Plus: {
|
||||||
|
"web_get_system_info": {
|
||||||
|
"ipaddress": "172.19.203.183",
|
||||||
|
"system_mode": "GNU/Linux",
|
||||||
|
"netmask": "255.255.255.0",
|
||||||
|
"gateway": "",
|
||||||
|
"Algorithm": "Scrypt",
|
||||||
|
"system_kernel_version": "4.4.194 #1 SMP Sat Sep 7 16:59:20 CST 2024",
|
||||||
|
"system_filesystem_version": "DG1+_SW_V1.0.2",
|
||||||
|
"nettype": "DHCP",
|
||||||
|
"dnsservers": "",
|
||||||
|
"netdevice": "eth0",
|
||||||
|
"minertype": "DG1+",
|
||||||
|
"macaddr": "12:34:56:78:90:12",
|
||||||
|
"firmware_type": "Release",
|
||||||
|
"hostname": "DG1+",
|
||||||
|
},
|
||||||
|
"web_summary": {
|
||||||
|
"STATUS": {
|
||||||
|
"STATUS": "S",
|
||||||
|
"when": 2557706,
|
||||||
|
"timestamp": 1731569527,
|
||||||
|
"api_version": "1.0.0",
|
||||||
|
"Msg": "summary",
|
||||||
|
},
|
||||||
|
"SUMMARY": [
|
||||||
|
{
|
||||||
|
"rate_unit": "MH/s",
|
||||||
|
"elapsed": 357357,
|
||||||
|
"rate_30m": 0,
|
||||||
|
"rate_5s": 14920.940000000001,
|
||||||
|
"bestshare": 0,
|
||||||
|
"rate_ideal": 14229,
|
||||||
|
"status": [
|
||||||
|
{"status": "s", "type": "rate", "msg": "", "code": 0},
|
||||||
|
{"status": "s", "type": "network", "msg": "", "code": 0},
|
||||||
|
{"status": "s", "type": "fans", "msg": "", "code": 0},
|
||||||
|
{"status": "s", "type": "temp", "msg": "", "code": 0},
|
||||||
|
],
|
||||||
|
"hw_all": 14199.040000000001,
|
||||||
|
"rate_avg": 14199.040000000001,
|
||||||
|
"rate_15m": 14415,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"INFO": {
|
||||||
|
"miner_version": "DG1+_SW_V1.0.2",
|
||||||
|
"CompileTime": "",
|
||||||
|
"dev_sn": "28HY245192N000245C23B",
|
||||||
|
"type": "DG1+",
|
||||||
|
"hw_version": "DG1+_HW_V1.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"web_stats": {
|
||||||
|
"STATUS": {
|
||||||
|
"STATUS": "S",
|
||||||
|
"when": 2557700,
|
||||||
|
"timestamp": 1731569521,
|
||||||
|
"api_version": "1.0.0",
|
||||||
|
"Msg": "stats",
|
||||||
|
},
|
||||||
|
"INFO": {
|
||||||
|
"miner_version": "DG1+_SW_V1.0.2",
|
||||||
|
"CompileTime": "",
|
||||||
|
"dev_sn": "28HY245192N000245C23B",
|
||||||
|
"type": "DG1+",
|
||||||
|
"hw_version": "DG1+_HW_V1.0",
|
||||||
|
},
|
||||||
|
"STATS": [
|
||||||
|
{
|
||||||
|
"rate_unit": "MH/s",
|
||||||
|
"elapsed": 357352,
|
||||||
|
"rate_30m": 0,
|
||||||
|
"rate_5s": 11531.879999999999,
|
||||||
|
"hwp_total": 0.11550000000000001,
|
||||||
|
"rate_ideal": 14229,
|
||||||
|
"chain": [
|
||||||
|
{
|
||||||
|
"freq_avg": 62000,
|
||||||
|
"index": 0,
|
||||||
|
"sn": "13HY245156N000581H11JB52",
|
||||||
|
"temp_chip": ["47125", "50500", "", ""],
|
||||||
|
"eeprom_loaded": True,
|
||||||
|
"rate_15m": 3507,
|
||||||
|
"hw": 204,
|
||||||
|
"temp_pcb": [47, 46, 67, 66],
|
||||||
|
"failrate": 0.029999999999999999,
|
||||||
|
"asic": "ooooooooo oooooooo oooooooo oooooooo oooo",
|
||||||
|
"rate_real": 3553.5,
|
||||||
|
"asic_num": 204,
|
||||||
|
"temp_pic": [47, 46, 67, 66],
|
||||||
|
"rate_ideal": 3557.25,
|
||||||
|
"hashrate": 3278.5999999999999,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"freq_avg": 62000,
|
||||||
|
"index": 1,
|
||||||
|
"sn": "13HY245156N000579H11JB52",
|
||||||
|
"temp_chip": ["52812", "56937", "", ""],
|
||||||
|
"eeprom_loaded": True,
|
||||||
|
"rate_15m": 3736,
|
||||||
|
"hw": 204,
|
||||||
|
"temp_pcb": [47, 46, 67, 66],
|
||||||
|
"failrate": 0.02,
|
||||||
|
"asic": "ooooooooo oooooooo oooooooo oooooooo oooo",
|
||||||
|
"rate_real": 3550.1100000000001,
|
||||||
|
"asic_num": 204,
|
||||||
|
"temp_pic": [47, 46, 67, 66],
|
||||||
|
"rate_ideal": 3557.25,
|
||||||
|
"hashrate": 3491.8400000000001,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"freq_avg": 62000,
|
||||||
|
"index": 2,
|
||||||
|
"sn": "13HY245156N000810H11JB52",
|
||||||
|
"temp_chip": ["48312", "51687", "", ""],
|
||||||
|
"eeprom_loaded": True,
|
||||||
|
"rate_15m": 3531,
|
||||||
|
"hw": 204,
|
||||||
|
"temp_pcb": [47, 46, 67, 66],
|
||||||
|
"failrate": 0.51000000000000001,
|
||||||
|
"asic": "ooooooooo oooooooo oooooooo oooooooo oooo",
|
||||||
|
"rate_real": 3551.8000000000002,
|
||||||
|
"asic_num": 204,
|
||||||
|
"temp_pic": [47, 46, 67, 66],
|
||||||
|
"rate_ideal": 3557.25,
|
||||||
|
"hashrate": 3408.6999999999998,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"freq_avg": 62000,
|
||||||
|
"index": 3,
|
||||||
|
"sn": "13HY245156N000587H11JB52",
|
||||||
|
"temp_chip": ["46500", "49062", "", ""],
|
||||||
|
"eeprom_loaded": True,
|
||||||
|
"rate_15m": 3641,
|
||||||
|
"hw": 204,
|
||||||
|
"temp_pcb": [47, 46, 67, 66],
|
||||||
|
"failrate": 0.029999999999999999,
|
||||||
|
"asic": "ooooooooo oooooooo oooooooo oooooooo oooo",
|
||||||
|
"rate_real": 3543.6300000000001,
|
||||||
|
"asic_num": 204,
|
||||||
|
"temp_pic": [47, 46, 67, 66],
|
||||||
|
"rate_ideal": 3557.25,
|
||||||
|
"hashrate": 3463.6799999999998,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"rate_15m": 14415,
|
||||||
|
"chain_num": 4,
|
||||||
|
"fan": ["5340", "5400", "5400", "5400"],
|
||||||
|
"rate_avg": 14199.040000000001,
|
||||||
|
"fan_num": 4,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"web_get_blink_status": {"blink": False},
|
||||||
|
"web_get_miner_conf": {
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"url": "stratum+tcp://ltc.trustpool.ru:3333",
|
||||||
|
"pass": "123",
|
||||||
|
"user": "Nikita9231.fworker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "stratum+tcp://ltc.trustpool.ru:443",
|
||||||
|
"pass": "123",
|
||||||
|
"user": "Nikita9231.fworker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "stratum+tcp://ltc.trustpool.ru:25",
|
||||||
|
"pass": "123",
|
||||||
|
"user": "Nikita9231.fworker",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"fc-voltage": "1470",
|
||||||
|
"fc-fan-ctrl": False,
|
||||||
|
"fc-freq-level": "100",
|
||||||
|
"fc-fan-pwm": "80",
|
||||||
|
"algo": "ltc",
|
||||||
|
"fc-work-mode": 0,
|
||||||
|
"fc-freq": "1850",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestElphapexMiners(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, "1.0.0")
|
||||||
|
self.assertEqual(result.fw_ver, "1.0.2")
|
||||||
|
self.assertEqual(result.hostname, "DG1+")
|
||||||
|
self.assertEqual(round(result.hashrate.into(ScryptUnit.MH)), 14199)
|
||||||
|
self.assertEqual(
|
||||||
|
result.fans,
|
||||||
|
[Fan(speed=5340), Fan(speed=5400), Fan(speed=5400), Fan(speed=5400)],
|
||||||
|
)
|
||||||
|
self.assertEqual(result.total_chips, result.expected_chips)
|
||||||
Reference in New Issue
Block a user