Compare commits

...

38 Commits

Author SHA1 Message Date
Upstream Data
c8a8315ad0 version: bump version number. 2024-06-04 09:28:15 -06:00
Upstream Data
dac9bcc3de refactor: improve _get_data implementation slightly. 2024-06-04 08:53:25 -06:00
Brett Rowan
46621d6b93 Merge pull request #151 from Ytemiloluwa/master 2024-06-02 13:50:51 -06:00
ytemiloluwa
35700f7e57 pools: MinerData 2024-06-02 20:47:58 +01:00
ytemiloluwa
08e6744595 index: unused variable 2024-06-02 07:26:43 +01:00
ytemiloluwa
2de3e5e328 update: _get_pools method 2024-06-02 07:13:39 +01:00
Temi
51f2eb1b1d Merge branch 'UpstreamData:master' into master 2024-06-02 06:51:28 +01:00
Brett Rowan
d40d92c1ca version: bump version number. 2024-05-28 21:46:41 -06:00
Brett Rowan
7ea63643a9 bug: fix a bunch of spelling mistakes. 2024-05-28 21:46:13 -06:00
upstreamdata
313c324771 bug: fix into naming on vnish with expected hashrate. 2024-05-27 15:57:06 -06:00
upstreamdata
a9fd9343d8 feature: add vnish fault light. 2024-05-26 21:59:59 -06:00
upstreamdata
8f41d4d0bc bug: fix MRO with vnish. 2024-05-26 21:59:57 -06:00
ytemiloluwa
521853863b base: _get_pools and get_pools 2024-05-26 13:21:53 +01:00
ytemiloluwa
b7a5a647b3 pools: DATA LOC 2024-05-26 00:36:07 +01:00
ytemiloluwa
4434f9ccad get_data: kwarg key 2024-05-26 00:22:57 +01:00
ytemiloluwa
82a1cc3cfe backends: _get_pools in antminer 2024-05-25 23:08:33 +01:00
Brett Rowan
6f10c91482 Merge pull request #150 from UpstreamData/snyk-fix-c1d73ce3e71f8a7a86f4d2be00564ee4 2024-05-25 09:00:39 -06:00
snyk-bot
f2d6bce165 fix: docs/requirements.txt to reduce vulnerabilities
The following vulnerabilities are fixed by pinning transitive dependencies:
- https://snyk.io/vuln/SNYK-PYTHON-JINJA2-6809379
2024-05-25 02:29:08 +00:00
Brett Rowan
61623cc44d version: bump version number. 2024-05-19 11:41:43 -06:00
Brett Rowan
a30a726324 bug: fix some issues with boser handlers. 2024-05-19 11:41:10 -06:00
Brett Rowan
0e90ad64cd Merge pull request #145 from Ytemiloluwa/master 2024-05-18 11:08:31 -06:00
ytemiloluwa
53572c6236 add new attributes 2024-05-18 16:53:06 +01:00
Brett Rowan
67da56a03b version: bump version number. 2024-05-17 21:06:23 -06:00
Brett Rowan
be8633185d bug: remove StrEnum references. 2024-05-17 21:02:57 -06:00
Brett Rowan
1d656da2a2 bug: remove StrEnum references. 2024-05-17 21:00:43 -06:00
Upstream Data
189deae3d1 version: bump version number. 2024-05-17 09:05:00 -06:00
Brett Rowan
46188ad52b Merge pull request #146 from UpstreamData/fix_typo
fix typo
2024-05-17 09:02:22 -06:00
John-Paul Compagnone
19e232ddb9 fix typo 2024-05-17 11:01:01 -04:00
ytemiloluwa
5d90b7e938 pools: updated metrics dataclass 2024-05-16 20:20:53 +01:00
ytemiloluwa
3f90799544 update metrics dataclass 2024-05-16 19:25:48 +01:00
Temi
1f70ec0d28 Merge branch 'UpstreamData:master' into master 2024-05-16 19:00:45 +01:00
Upstream Data
c26b78aa01 version: bump version number. 2024-05-16 08:27:38 -06:00
Brett Rowan
0debf16f7c Merge pull request #144 from jpcomps/master
ePIC: bug fixes
2024-05-16 08:27:07 -06:00
John-Paul Compagnone
d7f48d8f9f remove tuned from MinerConfig 2024-05-16 10:26:04 -04:00
ytemiloluwa
58c95559dd pools: dataclass metrics 2024-05-16 14:08:00 +01:00
JP Compagnone
03caa9fe94 Merge branch 'UpstreamData:master' into master 2024-05-15 21:03:32 -04:00
John-Paul Compagnone
afd8697f07 change from optimized to tuned, which is more generic for other FW 2024-05-15 21:03:12 -04:00
John-Paul Compagnone
91d504fc1c add Vopt status, fix typo 2024-05-14 23:07:34 -04:00
27 changed files with 1493 additions and 67 deletions

1180
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -444,7 +444,7 @@ class MiningModeConfig(MinerConfigOption):
) )
else: else:
return cls.hashrate_tuning( return cls.hashrate_tuning(
hashrate=algo_info["ChipTune"]["Target"], hashrate=algo_info["ChipTune"].get("Target"),
algo=TunerAlgo.chip_tune(), algo=TunerAlgo.chip_tune(),
) )
else: else:

View File

@@ -29,6 +29,7 @@ from .device import DeviceInfo
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
from .fans import Fan from .fans import Fan
from .hashrate import AlgoHashRate, HashUnit from .hashrate import AlgoHashRate, HashUnit
from pyasic.data.pools import PoolMetrics
@dataclass @dataclass
@@ -71,6 +72,7 @@ class MinerData:
fault_light: Whether the fault light is on as a boolean. fault_light: Whether the fault light is on as a boolean.
efficiency: Efficiency of the miner in J/TH (Watts per TH/s). Calculated automatically. efficiency: Efficiency of the miner in J/TH (Watts per TH/s). Calculated automatically.
is_mining: Whether the miner is mining. is_mining: Whether the miner is mining.
pools: A list of PoolMetrics instances, each representing metrics for a pool.
""" """
# general # general
@@ -143,6 +145,9 @@ class MinerData:
uptime: int = None uptime: int = None
efficiency: int = field(init=False) efficiency: int = field(init=False)
# pools
pools: list[PoolMetrics] = field(default_factory=list)
@classmethod @classmethod
def fields(cls): def fields(cls):
return [f.name for f in fields(cls) if not f.name.startswith("_")] return [f.name for f in fields(cls) if not f.name.startswith("_")]

59
pyasic/data/pools.py Normal file
View File

@@ -0,0 +1,59 @@
from dataclasses import dataclass, field
@dataclass
class PoolMetrics:
"""A dataclass to standardize pool metrics returned from miners.
Attributes:
accepted: Number of accepted shares.
rejected: Number of rejected shares.
get_failures: Number of failures in obtaining work from the pool.
remote_failures: Number of failures communicating with the pool server.
active: Indicates if the miner is connected to the stratum server.
Alive : Indicates if a pool is alive.
url: URL of the pool.
index: Index of the pool.
user: Username for the pool.
pool_rejected_percent: Percentage of rejected shares by the pool.
pool_stale_percent: Percentage of stale shares by the pool.
"""
accepted: int = None
rejected: int = None
get_failures: int = None
remote_failures: int = None
active: bool = None
alive: bool = None
url: str = None
index: int = None
user: str = None
pool_rejected_percent: float = field(init=False)
pool_stale_percent: float = field(init=False)
@property
def pool_rejected_percent(self) -> float: # noqa - Skip PyCharm inspection
"""Calculate and return the percentage of rejected shares"""
return self._calculate_percentage(self.rejected, self.accepted + self.rejected)
@pool_rejected_percent.setter
def pool_rejected_percent(self, val):
pass
@property
def pool_stale_percent(self) -> float: # noqa - Skip PyCharm inspection
"""Calculate and return the percentage of stale shares."""
return self._calculate_percentage(
self.get_failures, self.accepted + self.rejected
)
@pool_stale_percent.setter
def pool_stale_percent(self, val):
pass
@staticmethod
def _calculate_percentage(value: int, total: int) -> float:
"""Calculate the percentage."""
if total == 0:
return 0
return (value / total) * 100

View File

@@ -14,10 +14,10 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from enum import StrEnum from enum import Enum
class MinerFirmware(StrEnum): class MinerFirmware(str, Enum):
STOCK = "Stock" STOCK = "Stock"
BRAIINS_OS = "BOS+" BRAIINS_OS = "BOS+"
VNISH = "VNish" VNISH = "VNish"
@@ -25,3 +25,6 @@ class MinerFirmware(StrEnum):
HIVEON = "Hive" HIVEON = "Hive"
LUXOS = "LuxOS" LUXOS = "LuxOS"
MARATHON = "MaraFW" MARATHON = "MaraFW"
def __str__(self):
return self.value

View File

@@ -14,10 +14,10 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from enum import StrEnum from enum import Enum
class MinerMake(StrEnum): class MinerMake(str, Enum):
WHATSMINER = "WhatsMiner" WHATSMINER = "WhatsMiner"
ANTMINER = "AntMiner" ANTMINER = "AntMiner"
AVALONMINER = "AvalonMiner" AVALONMINER = "AvalonMiner"
@@ -25,3 +25,6 @@ class MinerMake(StrEnum):
GOLDSHELL = "Goldshell" GOLDSHELL = "Goldshell"
AURADINE = "Auradine" AURADINE = "Auradine"
EPIC = "ePIC" EPIC = "ePIC"
def __str__(self):
return self.value

View File

@@ -1,7 +1,7 @@
from enum import StrEnum from enum import Enum
class AntminerModels(StrEnum): class AntminerModels(str, Enum):
D3 = "D3" D3 = "D3"
HS3 = "HS3" HS3 = "HS3"
L3Plus = "L3+" L3Plus = "L3+"
@@ -46,8 +46,11 @@ class AntminerModels(StrEnum):
S21 = "S21" S21 = "S21"
T21 = "T21" T21 = "T21"
def __str__(self):
return self.value
class WhatsminerModels(StrEnum):
class WhatsminerModels(str, Enum):
M20V10 = "M20 V10" M20V10 = "M20 V10"
M20SV10 = "M20S V10" M20SV10 = "M20S V10"
M20SV20 = "M20S V20" M20SV20 = "M20S V20"
@@ -263,8 +266,11 @@ class WhatsminerModels(StrEnum):
M66SVK30 = "M66S VK30" M66SVK30 = "M66S VK30"
M66SVK40 = "M66S VK40" M66SVK40 = "M66S VK40"
def __str__(self):
return self.value
class AvalonminerModels(StrEnum):
class AvalonminerModels(str, Enum):
Avalon721 = "Avalon 721" Avalon721 = "Avalon 721"
Avalon741 = "Avalon 741" Avalon741 = "Avalon 741"
Avalon761 = "Avalon 761" Avalon761 = "Avalon 761"
@@ -278,13 +284,19 @@ class AvalonminerModels(StrEnum):
Avalon1166Pro = "Avalon 1166 Pro" Avalon1166Pro = "Avalon 1166 Pro"
Avalon1246 = "Avalon 1246" Avalon1246 = "Avalon 1246"
def __str__(self):
return self.value
class InnosiliconModels(StrEnum):
class InnosiliconModels(str, Enum):
T3HPlus = "T3H+" T3HPlus = "T3H+"
A10X = "A10X" A10X = "A10X"
def __str__(self):
return self.value
class GoldshellModels(StrEnum):
class GoldshellModels(str, Enum):
CK5 = "CK5" CK5 = "CK5"
HS5 = "HS5" HS5 = "HS5"
KD5 = "KD5" KD5 = "KD5"
@@ -292,13 +304,19 @@ class GoldshellModels(StrEnum):
KDBoxII = "KD Box II" KDBoxII = "KD Box II"
KDBoxPro = "KD Box Pro" KDBoxPro = "KD Box Pro"
def __str__(self):
return self.value
class ePICModels(StrEnum):
class ePICModels(str, Enum):
BM520i = "BlockMiner 520i" BM520i = "BlockMiner 520i"
BM720i = "BlockMiner 720i" BM720i = "BlockMiner 720i"
def __str__(self):
return self.value
class AuradineModels(StrEnum):
class AuradineModels(str, Enum):
AT1500 = "AT1500" AT1500 = "AT1500"
AT2860 = "AT2860" AT2860 = "AT2860"
AT2880 = "AT2880" AT2880 = "AT2880"
@@ -307,6 +325,9 @@ class AuradineModels(StrEnum):
AD2500 = "AD2500" AD2500 = "AD2500"
AD3500 = "AD3500" AD3500 = "AD3500"
def __str__(self):
return self.value
class MinerModel: class MinerModel:
ANTMINER = AntminerModels ANTMINER = AntminerModels

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 VNish
from pyasic.miners.device.models import S21
class VNishS21(VNish, S21):
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 .S21 import VNishS21

View File

@@ -18,3 +18,4 @@ from .X3 import *
from .X7 import * from .X7 import *
from .X17 import * from .X17 import *
from .X19 import * from .X19 import *
from .X21 import *

View File

@@ -19,7 +19,6 @@ from typing import List, Optional, Union
from pyasic.config import MinerConfig, MiningModeConfig from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import AlgoHashRate, Fan, 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.errors import APIError
from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.backends.bmminer import BMMiner
from pyasic.miners.backends.cgminer import CGMiner from pyasic.miners.backends.cgminer import CGMiner
from pyasic.miners.data import ( from pyasic.miners.data import (
@@ -32,6 +31,8 @@ from pyasic.miners.data import (
from pyasic.rpc.antminer import AntminerRPCAPI from pyasic.rpc.antminer import AntminerRPCAPI
from pyasic.ssh.antminer import AntminerModernSSH from pyasic.ssh.antminer import AntminerModernSSH
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
from pyasic.data.pools import PoolMetrics
from pyasic.errors import APIError
ANTMINER_MODERN_DATA_LOC = DataLocations( ANTMINER_MODERN_DATA_LOC = DataLocations(
**{ **{
@@ -79,6 +80,10 @@ ANTMINER_MODERN_DATA_LOC = DataLocations(
"_get_uptime", "_get_uptime",
[RPCAPICommand("rpc_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_pools", "pools")],
),
} }
) )
@@ -275,7 +280,7 @@ class AntminerModern(BMMiner):
rate_unit = "GH" rate_unit = "GH"
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
expected_rate, HashUnit.SHA256.from_str(rate_unit) expected_rate, HashUnit.SHA256.from_str(rate_unit)
).int(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass
@@ -351,6 +356,35 @@ class AntminerModern(BMMiner):
except LookupError: except LookupError:
pass pass
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
if rpc_pools is None:
try:
rpc_pools = await self.rpc.pools()
except APIError:
pass
pools_data = []
if rpc_pools is not None:
try:
pools = rpc_pools.get("POOLS", [])
for pool_info in pools:
pool_data = PoolMetrics(
accepted=pool_info.get("Accepted"),
rejected=pool_info.get("Rejected"),
get_failures=pool_info.get("Get Failures"),
remote_failures=pool_info.get("Remote Failures"),
active=pool_info.get("Stratum Active"),
alive=pool_info.get("Status") == "Alive",
url=pool_info.get("URL"),
user=pool_info.get("User"),
index=pool_info.get("POOL")
)
pools_data.append(pool_data)
except LookupError:
pass
return pools_data
ANTMINER_OLD_DATA_LOC = DataLocations( ANTMINER_OLD_DATA_LOC = DataLocations(
**{ **{

View File

@@ -251,7 +251,7 @@ class AvalonMiner(CGMiner):
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
parsed_stats["GHSmm"], HashUnit.SHA256.GH parsed_stats["GHSmm"], HashUnit.SHA256.GH
).int(self.algo.unit.default) ).into(self.algo.unit.default)
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
pass pass

View File

@@ -224,6 +224,6 @@ class BFGMiner(StockFirmware):
rate_unit = "GH" rate_unit = "GH"
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
expected_rate, HashUnit.SHA256.from_str(rate_unit) expected_rate, HashUnit.SHA256.from_str(rate_unit)
).int(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -240,7 +240,7 @@ class BMMiner(StockFirmware):
rate_unit = "GH" rate_unit = "GH"
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
expected_rate, HashUnit.SHA256.from_str(rate_unit) expected_rate, HashUnit.SHA256.from_str(rate_unit)
).int(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -865,7 +865,9 @@ class BOSer(BraiinsOSFirmware):
) -> Optional[int]: ) -> Optional[int]:
if grpc_active_performance_mode is None: if grpc_active_performance_mode is None:
try: try:
grpc_active_performance_mode = self.web.get_active_performance_mode() grpc_active_performance_mode = (
await self.web.get_active_performance_mode()
)
except APIError: except APIError:
pass pass

View File

@@ -575,7 +575,7 @@ class BTMiner(StockFirmware):
if expected_hashrate: if expected_hashrate:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
expected_hashrate, HashUnit.SHA256.GH expected_hashrate, HashUnit.SHA256.GH
).int(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -270,7 +270,7 @@ class ePIC(ePICFirmware):
ideal = hb["Hashrate"][1] / 100 ideal = hb["Hashrate"][1] / 100
hashrate += hb["Hashrate"][0] / ideal hashrate += hb["Hashrate"][0] / ideal
return AlgoHashRate.SHA256(hashrate, HashUnit.SHA256.GH).int( return AlgoHashRate.SHA256(hashrate, HashUnit.SHA256.MH).into(
self.algo.unit.default self.algo.unit.default
) )
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
@@ -317,7 +317,7 @@ class ePIC(ePICFirmware):
except APIError: except APIError:
pass pass
if web_capabilities is not None: if web_capabilities is None:
try: try:
web_capabilities = await self.web.capabilities() web_capabilities = await self.web.capabilities()
except APIError: except APIError:

View File

@@ -279,7 +279,7 @@ class LUXMiner(LuxOSFirmware):
rate_unit = "GH" rate_unit = "GH"
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
expected_rate, HashUnit.SHA256.from_str(rate_unit) expected_rate, HashUnit.SHA256.from_str(rate_unit)
).int(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -282,7 +282,7 @@ class MaraMiner(MaraFirmware):
try: try:
return AlgoHashRate.SHA256( return AlgoHashRate.SHA256(
web_brief["hashrate_ideal"], HashUnit.SHA256.GH web_brief["hashrate_ideal"], HashUnit.SHA256.GH
).int(self.algo.unit.default) ).into(self.algo.unit.default)
except LookupError: except LookupError:
pass pass

View File

@@ -84,7 +84,7 @@ VNISH_DATA_LOC = DataLocations(
) )
class VNish(BMMiner, VNishFirmware): class VNish(VNishFirmware, BMMiner):
"""Handler for VNish miners""" """Handler for VNish miners"""
_web_cls = VNishWebAPI _web_cls = VNishWebAPI
@@ -147,6 +147,22 @@ class VNish(BMMiner, VNishFirmware):
except KeyError: except KeyError:
pass pass
async def fault_light_off(self) -> bool:
result = await self.web.find_miner()
if result is not None:
if result.get("on") is False:
return True
else:
await self.web.find_miner()
async def fault_light_on(self) -> bool:
result = await self.web.find_miner()
if result is not None:
if result.get("on") is True:
return True
else:
await self.web.find_miner()
async def _get_hostname(self, web_summary: dict = None) -> str: async def _get_hostname(self, web_summary: dict = None) -> str:
if web_summary is None: if web_summary is None:
web_info = await self.web.info() web_info = await self.web.info()

View File

@@ -22,6 +22,7 @@ from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard, MinerData from pyasic.data import Fan, HashBoard, MinerData
from pyasic.data.device import DeviceInfo from pyasic.data.device import DeviceInfo
from pyasic.data.error_codes import MinerErrorData from pyasic.data.error_codes import MinerErrorData
from pyasic.data.pools import PoolMetrics
from pyasic.device import MinerModel from pyasic.device import MinerModel
from pyasic.device.algorithm import MinerAlgo from pyasic.device.algorithm import MinerAlgo
from pyasic.device.firmware import MinerFirmware from pyasic.device.firmware import MinerFirmware
@@ -341,6 +342,14 @@ class MinerProtocol(Protocol):
""" """
return await self._get_uptime() return await self._get_uptime()
async def get_pools(self) -> List[PoolMetrics]:
"""Get the pools information from Miner.
Return:
The pool information of the miner.
"""
return await self._get_pools()
async def _get_mac(self) -> Optional[str]: async def _get_mac(self) -> Optional[str]:
pass pass
@@ -392,56 +401,69 @@ class MinerProtocol(Protocol):
async def _get_uptime(self) -> Optional[int]: async def _get_uptime(self) -> Optional[int]:
pass pass
async def _get_pools(self) -> List[PoolMetrics]:
pass
async def _get_data( async def _get_data(
self, self,
allow_warning: bool, allow_warning: bool,
include: List[Union[str, DataOptions]] = None, include: List[Union[str, DataOptions]] = None,
exclude: List[Union[str, DataOptions]] = None, exclude: List[Union[str, DataOptions]] = None,
) -> dict: ) -> dict:
# handle include
if include is not None: if include is not None:
include = [str(i) for i in include] include = [str(i) for i in include]
else: else:
# everything # everything
include = [str(enum_value.value) for enum_value in DataOptions] include = [str(enum_value.value) for enum_value in DataOptions]
# handle exclude
# prioritized over include, including x and excluding x will exclude x
if exclude is not None: if exclude is not None:
for item in exclude: for item in exclude:
if str(item) in include: if str(item) in include:
include.remove(str(item)) include.remove(str(item))
api_multicommand = set() rpc_multicommand = set()
web_multicommand = [] web_multicommand = set()
# create multicommand
for data_name in include: for data_name in include:
try: try:
# get kwargs needed for the _get_xyz function
fn_args = getattr(self.data_locations, data_name).kwargs fn_args = getattr(self.data_locations, data_name).kwargs
# keep track of which RPC/Web commands need to be sent
for arg in fn_args: for arg in fn_args:
if isinstance(arg, RPCAPICommand): if isinstance(arg, RPCAPICommand):
api_multicommand.add(arg.cmd) rpc_multicommand.add(arg.cmd)
if isinstance(arg, WebAPICommand): if isinstance(arg, WebAPICommand):
if arg.cmd not in web_multicommand: web_multicommand.add(arg.cmd)
web_multicommand.append(arg.cmd)
except KeyError as e: except KeyError as e:
logger.error(e, data_name) logger.error(type(e), e, data_name)
continue continue
if len(api_multicommand) > 0: # create tasks for all commands that need to be sent, or no-op with sleep(0) -> None
api_command_task = asyncio.create_task( if len(rpc_multicommand) > 0:
self.api.multicommand(*api_multicommand, allow_warning=allow_warning) rpc_command_task = asyncio.create_task(
self.rpc.multicommand(*rpc_multicommand, allow_warning=allow_warning)
) )
else: else:
api_command_task = asyncio.sleep(0) rpc_command_task = asyncio.create_task(asyncio.sleep(0))
if len(web_multicommand) > 0: if len(web_multicommand) > 0:
web_command_task = asyncio.create_task( web_command_task = asyncio.create_task(
self.web.multicommand(*web_multicommand, allow_warning=allow_warning) self.web.multicommand(*web_multicommand, allow_warning=allow_warning)
) )
else: else:
web_command_task = asyncio.sleep(0) web_command_task = asyncio.create_task(asyncio.sleep(0))
web_command_data = await web_command_task # make sure the tasks complete
await asyncio.gather(rpc_command_task, web_command_task)
# grab data out of the tasks
web_command_data = web_command_task.result()
if web_command_data is None: if web_command_data is None:
web_command_data = {} web_command_data = {}
api_command_data = rpc_command_task.result()
api_command_data = await api_command_task
if api_command_data is None: if api_command_data is None:
api_command_data = {} api_command_data = {}

View File

@@ -38,6 +38,7 @@ class DataOptions(Enum):
UPTIME = "uptime" UPTIME = "uptime"
CONFIG = "config" CONFIG = "config"
VOLTAGE = "voltage" VOLTAGE = "voltage"
POOLS = "pools"
def __str__(self): def __str__(self):
return self.value return self.value

View File

@@ -384,6 +384,7 @@ MINER_CLASSES = {
"ANTMINER S19A": VNishS19a, "ANTMINER S19A": VNishS19a,
"ANTMINER S19A PRO": VNishS19aPro, "ANTMINER S19A PRO": VNishS19aPro,
"ANTMINER T19": VNishT19, "ANTMINER T19": VNishT19,
"ANTMINER S21": VNishS21,
}, },
MinerTypes.EPIC: { MinerTypes.EPIC: {
None: ePIC, None: ePIC,
@@ -530,7 +531,6 @@ class MinerFactory:
miner_type=miner_type, miner_type=miner_type,
miner_model=miner_model, miner_model=miner_model,
) )
return miner return miner
async def _get_miner_type(self, ip: str) -> MinerTypes | None: async def _get_miner_type(self, ip: str) -> MinerTypes | None:
@@ -673,7 +673,7 @@ class MinerFactory:
return MinerTypes.BRAIINS_OS return MinerTypes.BRAIINS_OS
if "BTMINER" in upper_data or "BITMICRO" in upper_data: if "BTMINER" in upper_data or "BITMICRO" in upper_data:
return MinerTypes.WHATSMINER return MinerTypes.WHATSMINER
if "VNISH" in upper_data: if "VNISH" in upper_data or "DEVICE PATH" in upper_data:
return MinerTypes.VNISH return MinerTypes.VNISH
if "HIVEON" in upper_data: if "HIVEON" in upper_data:
return MinerTypes.HIVEON return MinerTypes.HIVEON

View File

@@ -78,13 +78,16 @@ class BOSerWebAPI(BaseWebAPI):
for command in commands: for command in commands:
try: try:
tasks[command] = asyncio.create_task(getattr(self, command)()) tasks[command] = asyncio.create_task(getattr(self, command)())
except (APIError, AttributeError): except AttributeError:
result["command"] = {} pass
await asyncio.gather(*list(tasks.values())) await asyncio.gather(*[t for t in tasks.values()], return_exceptions=True)
for cmd in tasks: for cmd in tasks:
result[cmd] = tasks[cmd].result() try:
result[cmd] = await tasks[cmd]
except (GRPCError, APIError):
pass
return result return result
@@ -149,42 +152,55 @@ class BOSerWebAPI(BaseWebAPI):
) )
async def start(self) -> dict: async def start(self) -> dict:
return await self.send_command("start", message=StartRequest()) return await self.send_command("start", message=StartRequest(), privileged=True)
async def stop(self) -> dict: async def stop(self) -> dict:
return await self.send_command("stop", message=StopRequest()) return await self.send_command("stop", message=StopRequest(), privileged=True)
async def pause_mining(self) -> dict: async def pause_mining(self) -> dict:
return await self.send_command("pause_mining", message=PauseMiningRequest()) return await self.send_command(
"pause_mining", message=PauseMiningRequest(), privileged=True
)
async def resume_mining(self) -> dict: async def resume_mining(self) -> dict:
return await self.send_command("resume_mining", message=ResumeMiningRequest()) return await self.send_command(
"resume_mining", message=ResumeMiningRequest(), privileged=True
)
async def restart(self) -> dict: async def restart(self) -> dict:
return await self.send_command("restart", message=RestartRequest()) return await self.send_command(
"restart", message=RestartRequest(), privileged=True
)
async def reboot(self) -> dict: async def reboot(self) -> dict:
return await self.send_command("reboot", message=RebootRequest()) return await self.send_command(
"reboot", message=RebootRequest(), privileged=True
)
async def set_locate_device_status(self, enable: bool) -> dict: async def set_locate_device_status(self, enable: bool) -> dict:
return await self.send_command( return await self.send_command(
"set_locate_device_status", "set_locate_device_status",
message=SetLocateDeviceStatusRequest(enable=enable), message=SetLocateDeviceStatusRequest(enable=enable),
privileged=True,
) )
async def get_locate_device_status(self) -> dict: async def get_locate_device_status(self) -> dict:
return await self.send_command( return await self.send_command(
"get_locate_device_status", message=GetLocateDeviceStatusRequest() "get_locate_device_status",
message=GetLocateDeviceStatusRequest(),
privileged=True,
) )
async def set_password(self, password: str = None) -> dict: async def set_password(self, password: str = None) -> dict:
return await self.send_command( return await self.send_command(
"set_password", message=SetPasswordRequest(password=password) "set_password",
message=SetPasswordRequest(password=password),
privileged=True,
) )
async def get_cooling_state(self) -> dict: async def get_cooling_state(self) -> dict:
return await self.send_command( return await self.send_command(
"get_cooling_state", message=GetCoolingStateRequest() "get_cooling_state", message=GetCoolingStateRequest(), privileged=True
) )
async def set_immersion_mode( async def set_immersion_mode(
@@ -197,16 +213,17 @@ class BOSerWebAPI(BaseWebAPI):
message=SetImmersionModeRequest( message=SetImmersionModeRequest(
enable_immersion_mode=enable, save_action=save_action enable_immersion_mode=enable, save_action=save_action
), ),
privileged=True,
) )
async def get_tuner_state(self) -> dict: async def get_tuner_state(self) -> dict:
return await self.send_command( return await self.send_command(
"get_tuner_state", message=GetTunerStateRequest() "get_tuner_state", message=GetTunerStateRequest(), privileged=True
) )
async def list_target_profiles(self) -> dict: async def list_target_profiles(self) -> dict:
return await self.send_command( return await self.send_command(
"list_target_profiles", message=ListTargetProfilesRequest() "list_target_profiles", message=ListTargetProfilesRequest(), privileged=True
) )
async def set_default_power_target( async def set_default_power_target(
@@ -215,6 +232,7 @@ class BOSerWebAPI(BaseWebAPI):
return await self.send_command( return await self.send_command(
"set_default_power_target", "set_default_power_target",
message=SetDefaultPowerTargetRequest(save_action=save_action), message=SetDefaultPowerTargetRequest(save_action=save_action),
privileged=True,
) )
async def set_power_target( async def set_power_target(
@@ -227,6 +245,7 @@ class BOSerWebAPI(BaseWebAPI):
message=SetPowerTargetRequest( message=SetPowerTargetRequest(
power_target=Power(watt=power_target), save_action=save_action power_target=Power(watt=power_target), save_action=save_action
), ),
privileged=True,
) )
async def increment_power_target( async def increment_power_target(
@@ -240,6 +259,7 @@ class BOSerWebAPI(BaseWebAPI):
power_target_increment=Power(watt=power_target_increment), power_target_increment=Power(watt=power_target_increment),
save_action=save_action, save_action=save_action,
), ),
privileged=True,
) )
async def decrement_power_target( async def decrement_power_target(
@@ -253,6 +273,7 @@ class BOSerWebAPI(BaseWebAPI):
power_target_decrement=Power(watt=power_target_decrement), power_target_decrement=Power(watt=power_target_decrement),
save_action=save_action, save_action=save_action,
), ),
privileged=True,
) )
async def set_default_hashrate_target( async def set_default_hashrate_target(
@@ -261,6 +282,7 @@ class BOSerWebAPI(BaseWebAPI):
return await self.send_command( return await self.send_command(
"set_default_hashrate_target", "set_default_hashrate_target",
message=SetDefaultHashrateTargetRequest(save_action=save_action), message=SetDefaultHashrateTargetRequest(save_action=save_action),
privileged=True,
) )
async def set_hashrate_target( async def set_hashrate_target(
@@ -274,6 +296,7 @@ class BOSerWebAPI(BaseWebAPI):
hashrate_target=TeraHashrate(terahash_per_second=hashrate_target), hashrate_target=TeraHashrate(terahash_per_second=hashrate_target),
save_action=save_action, save_action=save_action,
), ),
privileged=True,
) )
async def increment_hashrate_target( async def increment_hashrate_target(
@@ -289,6 +312,7 @@ class BOSerWebAPI(BaseWebAPI):
), ),
save_action=save_action, save_action=save_action,
), ),
privileged=True,
) )
async def decrement_hashrate_target( async def decrement_hashrate_target(
@@ -304,6 +328,7 @@ class BOSerWebAPI(BaseWebAPI):
), ),
save_action=save_action, save_action=save_action,
), ),
privileged=True,
) )
async def set_dps( async def set_dps(
@@ -327,6 +352,7 @@ class BOSerWebAPI(BaseWebAPI):
) )
), ),
), ),
privileged=True,
) )
async def set_performance_mode( async def set_performance_mode(
@@ -356,6 +382,7 @@ class BOSerWebAPI(BaseWebAPI):
) )
), ),
), ),
privileged=True,
) )
if hashrate_target is not None: if hashrate_target is not None:
return await self.send_command( return await self.send_command(
@@ -372,16 +399,19 @@ class BOSerWebAPI(BaseWebAPI):
) )
), ),
), ),
privileged=True,
) )
async def get_active_performance_mode(self) -> dict: async def get_active_performance_mode(self) -> dict:
return await self.send_command( return await self.send_command(
"get_active_performance_mode", message=GetPerformanceModeRequest() "get_active_performance_mode",
message=GetPerformanceModeRequest(),
privileged=True,
) )
async def get_pool_groups(self) -> dict: async def get_pool_groups(self) -> dict:
return await self.send_command( return await self.send_command(
"get_pool_groups", message=GetPoolGroupsRequest() "get_pool_groups", message=GetPoolGroupsRequest(), privileged=True
) )
async def create_pool_group(self) -> dict: async def create_pool_group(self) -> dict:
@@ -395,40 +425,44 @@ class BOSerWebAPI(BaseWebAPI):
async def get_miner_configuration(self) -> dict: async def get_miner_configuration(self) -> dict:
return await self.send_command( return await self.send_command(
"get_miner_configuration", message=GetMinerConfigurationRequest() "get_miner_configuration",
message=GetMinerConfigurationRequest(),
privileged=True,
) )
async def get_constraints(self) -> dict: async def get_constraints(self) -> dict:
return await self.send_command( return await self.send_command(
"get_constraints", message=GetConstraintsRequest() "get_constraints", message=GetConstraintsRequest(), privileged=True
) )
async def get_license_state(self) -> dict: async def get_license_state(self) -> dict:
return await self.send_command( return await self.send_command(
"get_license_state", message=GetLicenseStateRequest() "get_license_state", message=GetLicenseStateRequest(), privileged=True
) )
async def get_miner_status(self) -> dict: async def get_miner_status(self) -> dict:
return await self.send_command( return await self.send_command(
"get_miner_status", message=GetMinerStatusRequest() "get_miner_status", message=GetMinerStatusRequest(), privileged=True
) )
async def get_miner_details(self) -> dict: async def get_miner_details(self) -> dict:
return await self.send_command( return await self.send_command(
"get_miner_details", message=GetMinerDetailsRequest() "get_miner_details", message=GetMinerDetailsRequest(), privileged=True
) )
async def get_miner_stats(self) -> dict: async def get_miner_stats(self) -> dict:
return await self.send_command( return await self.send_command(
"get_miner_stats", message=GetMinerStatsRequest() "get_miner_stats", message=GetMinerStatsRequest(), privileged=True
) )
async def get_hashboards(self) -> dict: async def get_hashboards(self) -> dict:
return await self.send_command("get_hashboards", message=GetHashboardsRequest()) return await self.send_command(
"get_hashboards", message=GetHashboardsRequest(), privileged=True
)
async def get_support_archive(self) -> dict: async def get_support_archive(self) -> dict:
return await self.send_command( return await self.send_command(
"get_support_archive", message=GetSupportArchiveRequest() "get_support_archive", message=GetSupportArchiveRequest(), privileged=True
) )
async def enable_hashboards( async def enable_hashboards(
@@ -441,6 +475,7 @@ class BOSerWebAPI(BaseWebAPI):
message=EnableHashboardsRequest( message=EnableHashboardsRequest(
hashboard_ids=hashboard_ids, save_action=save_action hashboard_ids=hashboard_ids, save_action=save_action
), ),
privileged=True,
) )
async def disable_hashboards( async def disable_hashboards(
@@ -453,4 +488,5 @@ class BOSerWebAPI(BaseWebAPI):
message=DisableHashboardsRequest( message=DisableHashboardsRequest(
hashboard_ids=hashboard_ids, save_action=save_action hashboard_ids=hashboard_ids, save_action=save_action
), ),
privileged=True,
) )

View File

@@ -140,3 +140,6 @@ class VNishWebAPI(BaseWebAPI):
async def autotune_presets(self) -> dict: async def autotune_presets(self) -> dict:
return await self.send_command("autotune/presets") return await self.send_command("autotune/presets")
async def find_miner(self) -> dict:
return await self.send_command("find-miner", privileged=True)

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "pyasic" name = "pyasic"
version = "0.57.0" version = "0.57.6"
description = "A simplified and standardized interface for Bitcoin ASICs." description = "A simplified and standardized interface for Bitcoin ASICs."
authors = ["UpstreamData <brett@upstreamdata.ca>"] authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic" repository = "https://github.com/UpstreamData/pyasic"

View File

@@ -57,6 +57,7 @@ class MinersTest(unittest.TestCase):
"wattage", "wattage",
"voltage", "voltage",
"wattage_limit", "wattage_limit",
"pools",
] ]
) )
warnings.filterwarnings("ignore") warnings.filterwarnings("ignore")