feature: add support for hammer D10 and hammer discovery
This commit is contained in:
@@ -27,6 +27,7 @@ class MinerMake(str, Enum):
|
|||||||
EPIC = "ePIC"
|
EPIC = "ePIC"
|
||||||
BITAXE = "BitAxe"
|
BITAXE = "BitAxe"
|
||||||
ICERIVER = "IceRiver"
|
ICERIVER = "IceRiver"
|
||||||
|
HAMMER = "Hammer"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|||||||
@@ -364,6 +364,13 @@ class IceRiverModels(str, Enum):
|
|||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
class HammerModels(str, Enum):
|
||||||
|
D10 = "D10"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class MinerModel:
|
class MinerModel:
|
||||||
ANTMINER = AntminerModels
|
ANTMINER = AntminerModels
|
||||||
WHATSMINER = WhatsminerModels
|
WHATSMINER = WhatsminerModels
|
||||||
@@ -374,3 +381,4 @@ class MinerModel:
|
|||||||
EPIC = ePICModels
|
EPIC = ePICModels
|
||||||
BITAXE = BitAxeModels
|
BITAXE = BitAxeModels
|
||||||
ICERIVER = IceRiverModels
|
ICERIVER = IceRiverModels
|
||||||
|
HAMMER = HammerModels
|
||||||
|
|||||||
@@ -17,16 +17,19 @@ from .antminer import AntminerModern, AntminerOld
|
|||||||
from .auradine import Auradine
|
from .auradine import Auradine
|
||||||
from .avalonminer import AvalonMiner
|
from .avalonminer import AvalonMiner
|
||||||
from .bfgminer import BFGMiner
|
from .bfgminer import BFGMiner
|
||||||
|
from .bitaxe import BitAxe
|
||||||
from .bmminer import BMMiner
|
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 .epic import ePIC
|
from .epic import ePIC
|
||||||
from .goldshell import GoldshellMiner
|
from .goldshell import GoldshellMiner
|
||||||
|
from .hammer import BlackMiner
|
||||||
from .hiveon import Hiveon
|
from .hiveon import Hiveon
|
||||||
from .iceriver import IceRiver
|
from .iceriver import IceRiver
|
||||||
from .innosilicon import Innosilicon
|
from .innosilicon import Innosilicon
|
||||||
from .luxminer import LUXMiner
|
from .luxminer import LUXMiner
|
||||||
from .marathon import MaraMiner
|
from .marathon import MaraMiner
|
||||||
|
from .unknown import UnknownMiner
|
||||||
from .vnish import VNish
|
from .vnish import VNish
|
||||||
from .whatsminer import M2X, M3X, M5X, M6X
|
from .whatsminer import M2X, M3X, M5X, M6X
|
||||||
|
|||||||
@@ -17,11 +17,10 @@
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from pyasic import MinerConfig
|
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.error_codes import MinerErrorData, X19Error
|
||||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.base import BaseMiner
|
|
||||||
from pyasic.miners.data import (
|
from pyasic.miners.data import (
|
||||||
DataFunction,
|
DataFunction,
|
||||||
DataLocations,
|
DataLocations,
|
||||||
@@ -29,6 +28,7 @@ from pyasic.miners.data import (
|
|||||||
RPCAPICommand,
|
RPCAPICommand,
|
||||||
WebAPICommand,
|
WebAPICommand,
|
||||||
)
|
)
|
||||||
|
from pyasic.miners.device.firmware import StockFirmware
|
||||||
from pyasic.rpc.ccminer import CCMinerRPCAPI
|
from pyasic.rpc.ccminer import CCMinerRPCAPI
|
||||||
from pyasic.web.hammer import HammerWebAPI
|
from pyasic.web.hammer import HammerWebAPI
|
||||||
|
|
||||||
@@ -50,6 +50,10 @@ HAMMER_DATA_LOC = DataLocations(
|
|||||||
"_get_hostname",
|
"_get_hostname",
|
||||||
[WebAPICommand("web_get_system_info", "get_system_info")],
|
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||||
),
|
),
|
||||||
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
|
"_get_hashboards",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
|
),
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate",
|
"_get_hashrate",
|
||||||
[RPCAPICommand("rpc_summary", "summary")],
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
@@ -86,7 +90,7 @@ HAMMER_DATA_LOC = DataLocations(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BlackMiner(BaseMiner):
|
class BlackMiner(StockFirmware):
|
||||||
"""Handler for Hammer miners."""
|
"""Handler for Hammer miners."""
|
||||||
|
|
||||||
_rpc_cls = CCMinerRPCAPI
|
_rpc_cls = CCMinerRPCAPI
|
||||||
@@ -127,6 +131,186 @@ class BlackMiner(BaseMiner):
|
|||||||
return True
|
return True
|
||||||
return False
|
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]:
|
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
|
||||||
if web_get_system_info is None:
|
if web_get_system_info is None:
|
||||||
try:
|
try:
|
||||||
@@ -180,42 +364,6 @@ class BlackMiner(BaseMiner):
|
|||||||
pass
|
pass
|
||||||
return errors
|
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(
|
async def _get_fault_light(
|
||||||
self, web_get_blink_status: dict = None
|
self, web_get_blink_status: dict = None
|
||||||
) -> Optional[bool]:
|
) -> Optional[bool]:
|
||||||
|
|||||||
@@ -52,3 +52,7 @@ class BitAxeMake(BaseMiner):
|
|||||||
|
|
||||||
class IceRiverMake(BaseMiner):
|
class IceRiverMake(BaseMiner):
|
||||||
make = MinerMake.ICERIVER
|
make = MinerMake.ICERIVER
|
||||||
|
|
||||||
|
|
||||||
|
class HammerMake(BaseMiner):
|
||||||
|
make = MinerMake.HAMMER
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from .auradine import *
|
|||||||
from .avalonminer import *
|
from .avalonminer import *
|
||||||
from .epic import *
|
from .epic import *
|
||||||
from .goldshell import *
|
from .goldshell import *
|
||||||
|
from .hammer import *
|
||||||
from .iceriver import *
|
from .iceriver import *
|
||||||
from .innosilicon import *
|
from .innosilicon import *
|
||||||
from .whatsminer import *
|
from .whatsminer import *
|
||||||
|
|||||||
21
pyasic/miners/device/models/hammer/DX/D10.py
Normal file
21
pyasic/miners/device/models/hammer/DX/D10.py
Normal 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
|
||||||
1
pyasic/miners/device/models/hammer/DX/__init__.py
Normal file
1
pyasic/miners/device/models/hammer/DX/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .D10 import D10
|
||||||
1
pyasic/miners/device/models/hammer/__init__.py
Normal file
1
pyasic/miners/device/models/hammer/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .DX import *
|
||||||
@@ -32,13 +32,12 @@ from pyasic.miners.antminer import *
|
|||||||
from pyasic.miners.auradine import *
|
from pyasic.miners.auradine import *
|
||||||
from pyasic.miners.avalonminer import *
|
from pyasic.miners.avalonminer import *
|
||||||
from pyasic.miners.backends 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.base import AnyMiner
|
||||||
from pyasic.miners.bitaxe import *
|
from pyasic.miners.bitaxe import *
|
||||||
from pyasic.miners.blockminer import *
|
from pyasic.miners.blockminer import *
|
||||||
from pyasic.miners.device.makes import *
|
from pyasic.miners.device.makes import *
|
||||||
from pyasic.miners.goldshell import *
|
from pyasic.miners.goldshell import *
|
||||||
|
from pyasic.miners.hammer import *
|
||||||
from pyasic.miners.iceriver import *
|
from pyasic.miners.iceriver import *
|
||||||
from pyasic.miners.innosilicon import *
|
from pyasic.miners.innosilicon import *
|
||||||
from pyasic.miners.whatsminer import *
|
from pyasic.miners.whatsminer import *
|
||||||
@@ -59,6 +58,7 @@ class MinerTypes(enum.Enum):
|
|||||||
MARATHON = 11
|
MARATHON = 11
|
||||||
BITAXE = 12
|
BITAXE = 12
|
||||||
ICERIVER = 13
|
ICERIVER = 13
|
||||||
|
HAMMER = 14
|
||||||
|
|
||||||
|
|
||||||
MINER_CLASSES = {
|
MINER_CLASSES = {
|
||||||
@@ -478,6 +478,10 @@ MINER_CLASSES = {
|
|||||||
"KS5L": IceRiverKS5L,
|
"KS5L": IceRiverKS5L,
|
||||||
"KS5M": IceRiverKS5M,
|
"KS5M": IceRiverKS5M,
|
||||||
},
|
},
|
||||||
|
MinerTypes.HAMMER: {
|
||||||
|
None: type("HammerUnknown", (BlackMiner, HammerMake), {}),
|
||||||
|
"HAMMER D10": HammerD10,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -627,6 +631,10 @@ class MinerFactory:
|
|||||||
"www-authenticate", ""
|
"www-authenticate", ""
|
||||||
):
|
):
|
||||||
return MinerTypes.ANTMINER
|
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:
|
if len(web_resp.history) > 0:
|
||||||
history_resp = web_resp.history[0]
|
history_resp = web_resp.history[0]
|
||||||
if (
|
if (
|
||||||
@@ -1130,6 +1138,21 @@ class MinerFactory:
|
|||||||
except httpx.HTTPError:
|
except httpx.HTTPError:
|
||||||
pass
|
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()
|
miner_factory = MinerFactory()
|
||||||
|
|
||||||
|
|||||||
1
pyasic/miners/hammer/__init__.py
Normal file
1
pyasic/miners/hammer/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .blackminer import *
|
||||||
6
pyasic/miners/hammer/blackminer/DX/D10.py
Normal file
6
pyasic/miners/hammer/blackminer/DX/D10.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pyasic.miners.backends import BlackMiner
|
||||||
|
from pyasic.miners.device.models import D10
|
||||||
|
|
||||||
|
|
||||||
|
class HammerD10(BlackMiner, D10):
|
||||||
|
pass
|
||||||
1
pyasic/miners/hammer/blackminer/DX/__init__.py
Normal file
1
pyasic/miners/hammer/blackminer/DX/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .D10 import HammerD10
|
||||||
1
pyasic/miners/hammer/blackminer/__init__.py
Normal file
1
pyasic/miners/hammer/blackminer/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .DX import *
|
||||||
Reference in New Issue
Block a user