feature: add support for hammer D10 and hammer discovery

This commit is contained in:
Upstream Data
2024-11-14 08:45:33 -07:00
parent 924b62e0d5
commit 261527a380
14 changed files with 261 additions and 41 deletions

View File

@@ -27,6 +27,7 @@ class MinerMake(str, Enum):
EPIC = "ePIC"
BITAXE = "BitAxe"
ICERIVER = "IceRiver"
HAMMER = "Hammer"
def __str__(self):
return self.value

View File

@@ -364,6 +364,13 @@ class IceRiverModels(str, Enum):
return self.value
class HammerModels(str, Enum):
D10 = "D10"
def __str__(self):
return self.value
class MinerModel:
ANTMINER = AntminerModels
WHATSMINER = WhatsminerModels
@@ -374,3 +381,4 @@ class MinerModel:
EPIC = ePICModels
BITAXE = BitAxeModels
ICERIVER = IceRiverModels
HAMMER = HammerModels

View File

@@ -17,16 +17,19 @@ from .antminer import AntminerModern, AntminerOld
from .auradine import Auradine
from .avalonminer import AvalonMiner
from .bfgminer import BFGMiner
from .bitaxe import BitAxe
from .bmminer import BMMiner
from .braiins_os import BOSer, BOSMiner
from .btminer import BTMiner
from .cgminer import CGMiner
from .epic import ePIC
from .goldshell import GoldshellMiner
from .hammer import BlackMiner
from .hiveon import Hiveon
from .iceriver import IceRiver
from .innosilicon import Innosilicon
from .luxminer import LUXMiner
from .marathon import MaraMiner
from .unknown import UnknownMiner
from .vnish import VNish
from .whatsminer import M2X, M3X, M5X, M6X

View File

@@ -17,11 +17,10 @@
from typing import List, Optional
from pyasic import MinerConfig
from pyasic.data import AlgoHashRate, HashBoard, HashUnit
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner
from pyasic.miners.data import (
DataFunction,
DataLocations,
@@ -29,6 +28,7 @@ from pyasic.miners.data import (
RPCAPICommand,
WebAPICommand,
)
from pyasic.miners.device.firmware import StockFirmware
from pyasic.rpc.ccminer import CCMinerRPCAPI
from pyasic.web.hammer import HammerWebAPI
@@ -50,6 +50,10 @@ HAMMER_DATA_LOC = DataLocations(
"_get_hostname",
[WebAPICommand("web_get_system_info", "get_system_info")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("rpc_summary", "summary")],
@@ -86,7 +90,7 @@ HAMMER_DATA_LOC = DataLocations(
)
class BlackMiner(BaseMiner):
class BlackMiner(StockFirmware):
"""Handler for Hammer miners."""
_rpc_cls = CCMinerRPCAPI
@@ -127,6 +131,186 @@ class BlackMiner(BaseMiner):
return True
return False
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
if rpc_version is None:
try:
rpc_version = await self.rpc.version()
except APIError:
pass
if rpc_version is not None:
try:
self.api_ver = rpc_version["VERSION"][0]["API"]
except LookupError:
pass
return self.api_ver
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
if rpc_version is None:
try:
rpc_version = await self.rpc.version()
except APIError:
pass
if rpc_version is not None:
try:
self.fw_ver = rpc_version["VERSION"][0]["CompileTime"]
except LookupError:
pass
return self.fw_ver
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
# get hr from API
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
except APIError:
pass
if rpc_summary is not None:
try:
return AlgoHashRate.SHA256(
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH
).into(self.algo.unit.default)
except (LookupError, ValueError, TypeError):
pass
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
hashboards = []
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_stats is not None:
try:
board_offset = -1
boards = rpc_stats["STATS"]
if len(boards) > 1:
for board_num in range(1, 16, 5):
for _b_num in range(5):
b = boards[1].get(f"chain_acn{board_num + _b_num}")
if b and not b == 0 and board_offset == -1:
board_offset = board_num
if board_offset == -1:
board_offset = 1
real_slots = []
for i in range(board_offset, board_offset + 4):
try:
key = f"chain_acs{i}"
if boards[1].get(key, "") != "":
real_slots.append(i)
except LookupError:
pass
if len(real_slots) < 3:
real_slots = list(
range(board_offset, board_offset + self.expected_hashboards)
)
for i in real_slots:
hashboard = HashBoard(
slot=i - board_offset, expected_chips=self.expected_chips
)
chip_temp = boards[1].get(f"temp{i}")
if chip_temp:
hashboard.chip_temp = round(chip_temp)
temp = boards[1].get(f"temp2_{i}")
if temp:
hashboard.temp = round(temp)
hashrate = boards[1].get(f"chain_rate{i}")
if hashrate:
hashboard.hashrate = AlgoHashRate.SHA256(
hashrate, HashUnit.SHA256.GH
).into(self.algo.unit.default)
chips = boards[1].get(f"chain_acn{i}")
if chips:
hashboard.chips = chips
hashboard.missing = False
if (not chips) or (not chips > 0):
hashboard.missing = True
hashboards.append(hashboard)
except (LookupError, ValueError, TypeError):
pass
return hashboards
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
fans = [Fan() for _ in range(self.expected_fans)]
if rpc_stats is not None:
try:
fan_offset = -1
for fan_num in range(1, 8, 4):
for _f_num in range(4):
f = rpc_stats["STATS"][1].get(f"fan{fan_num + _f_num}", 0)
if f and not f == 0 and fan_offset == -1:
fan_offset = fan_num
if fan_offset == -1:
fan_offset = 1
for fan in range(self.expected_fans):
fans[fan].speed = rpc_stats["STATS"][1].get(
f"fan{fan_offset+fan}", 0
)
except LookupError:
pass
return fans
async def _get_expected_hashrate(
self, rpc_stats: dict = None
) -> Optional[AlgoHashRate]:
# X19 method, not sure compatibility
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_stats is not None:
try:
expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
try:
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
except KeyError:
rate_unit = "GH"
return AlgoHashRate.SHA256(
expected_rate, HashUnit.SHA256.from_str(rate_unit)
).into(self.algo.unit.default)
except LookupError:
pass
async def _get_uptime(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 int(rpc_stats["STATS"][1]["Elapsed"])
except LookupError:
pass
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
if web_get_system_info is None:
try:
@@ -180,42 +364,6 @@ class BlackMiner(BaseMiner):
pass
return errors
async def _get_hashboards(self) -> List[HashBoard]:
hashboards = [
HashBoard(idx, expected_chips=self.expected_chips)
for idx in range(self.expected_hashboards)
]
try:
rpc_stats = await self.web.stats(new_api=True)
except APIError:
return hashboards
if rpc_stats is not None:
try:
for board in rpc_stats["STATS"][0]["chain"]:
hashboards[board["index"]].hashrate = AlgoHashRate.SHA256(
board["rate_real"], HashUnit.SHA256.GH
).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 == 0, board["temp_chip"])
)
hashboards[board["index"]].chip_temp = sum(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]:

View File

@@ -52,3 +52,7 @@ class BitAxeMake(BaseMiner):
class IceRiverMake(BaseMiner):
make = MinerMake.ICERIVER
class HammerMake(BaseMiner):
make = MinerMake.HAMMER

View File

@@ -19,6 +19,7 @@ from .auradine import *
from .avalonminer import *
from .epic import *
from .goldshell import *
from .hammer import *
from .iceriver import *
from .innosilicon import *
from .whatsminer import *

View File

@@ -0,0 +1,21 @@
# ------------------------------------------------------------------------------
# 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.models import MinerModel
from pyasic.miners.device.makes import HammerMake
class D10(HammerMake):
raw_model = MinerModel.HAMMER.D10

View File

@@ -0,0 +1 @@
from .D10 import D10

View File

@@ -0,0 +1 @@
from .DX import *

View File

@@ -32,13 +32,12 @@ from pyasic.miners.antminer import *
from pyasic.miners.auradine import *
from pyasic.miners.avalonminer import *
from pyasic.miners.backends import *
from pyasic.miners.backends.bitaxe import BitAxe
from pyasic.miners.backends.unknown import UnknownMiner
from pyasic.miners.base import AnyMiner
from pyasic.miners.bitaxe import *
from pyasic.miners.blockminer import *
from pyasic.miners.device.makes import *
from pyasic.miners.goldshell import *
from pyasic.miners.hammer import *
from pyasic.miners.iceriver import *
from pyasic.miners.innosilicon import *
from pyasic.miners.whatsminer import *
@@ -59,6 +58,7 @@ class MinerTypes(enum.Enum):
MARATHON = 11
BITAXE = 12
ICERIVER = 13
HAMMER = 14
MINER_CLASSES = {
@@ -478,6 +478,10 @@ MINER_CLASSES = {
"KS5L": IceRiverKS5L,
"KS5M": IceRiverKS5M,
},
MinerTypes.HAMMER: {
None: type("HammerUnknown", (BlackMiner, HammerMake), {}),
"HAMMER D10": HammerD10,
},
}
@@ -627,6 +631,10 @@ class MinerFactory:
"www-authenticate", ""
):
return MinerTypes.ANTMINER
if web_resp.status_code == 401 and 'realm="blackMiner' in web_resp.headers.get(
"www-authenticate", ""
):
return MinerTypes.HAMMER
if len(web_resp.history) > 0:
history_resp = web_resp.history[0]
if (
@@ -1130,6 +1138,21 @@ class MinerFactory:
except httpx.HTTPError:
pass
async def get_miner_model_hammer(self, ip: str) -> str | None:
auth = httpx.DigestAuth(
"root", settings.get("default_hammer_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()

View File

@@ -0,0 +1 @@
from .blackminer import *

View File

@@ -0,0 +1,6 @@
from pyasic.miners.backends import BlackMiner
from pyasic.miners.device.models import D10
class HammerD10(BlackMiner, D10):
pass

View File

@@ -0,0 +1 @@
from .D10 import HammerD10

View File

@@ -0,0 +1 @@
from .DX import *