Compare commits

...

43 Commits

Author SHA1 Message Date
Upstream Data
e690e6dd3b version: bump version number 2024-10-29 16:16:13 -06:00
James Hilliard
d4665ed768 Update betterproto and regenerate protoc files
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
2024-10-29 16:15:30 -06:00
James Hilliard
b90a92c0df Update betterproto and regenerate protoc files
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
2024-10-29 16:15:11 -06:00
Upstream Data
50cfcf9796 version: bump version number 2024-10-29 09:56:30 -06:00
Brett Rowan
5d204f09da Merge pull request #213 from jameshilliard/update-deps
Update dependencices
2024-10-29 09:56:07 -06:00
James Hilliard
4c0410322f Update dependencices 2024-10-29 09:53:33 -06:00
Upstream Data
fbb2b3f6e7 version: bump version number 2024-10-29 09:46:03 -06:00
Upstream Data
0f09fb49fc bug: fix inf and nan parsing issues by replacing them with 0 2024-10-29 09:35:19 -06:00
Upstream Data
b0d063d6ed feature: add support for Inno A11MX (no chip counts) 2024-10-29 09:15:57 -06:00
Upstream Data
a68fe70af4 bug: fix pool parsing failing with no scheme 2024-10-29 08:57:43 -06:00
Upstream Data
43c7ac281b feature: add support for KA3 2024-10-29 08:29:09 -06:00
Upstream Data
a97ae55a06 version: bump version number 2024-10-28 08:18:52 -06:00
Upstream Data
4a3a6f4186 feature: add get_mac for bitaxe 2024-10-28 08:18:15 -06:00
Brett Rowan
f976724ada version: bump version number 2024-10-25 14:12:34 -06:00
Brett Rowan
2632bdaa30 Merge pull request #208 from eandersson/braiin_fix 2024-10-25 14:11:33 -06:00
Erik Olof Gunnar Andersson
91016d7b8c Fix issue with BraiinsOS health check failing 2024-10-25 22:04:34 +02:00
Upstream Data
2b00e741ca version: bump version number
Fixes: #206
2024-10-08 08:16:25 -06:00
Upstream Data
d496c11d67 bug: fix some cases where Antminer online status couldnt be parsed
Re: #206
2024-10-08 08:16:25 -06:00
Upstream Data
5880223517 bug: fix goldshell issues with pools data 2024-10-08 08:16:25 -06:00
Brett Rowan
394a5dcd0d Merge pull request #204 from Ytemiloluwa/BFGMiner
feat: Add _get_pools method for BFGMiner(StockFirmware)
2024-10-01 08:49:40 -06:00
Upstream Data
7365275f46 version: bump version number. 2024-09-24 12:51:17 -06:00
Upstream Data
0ecab5fdd4 bug: fix an issue with moving board slots on BOS+. 2024-09-24 12:50:56 -06:00
ytemiloluwa
ed0d9f73e4 backends: add _get_pools method to bfgminer 2024-09-19 09:05:29 +01:00
Upstream Data
28f4e16662 version: bump version number. 2024-09-18 13:16:51 -06:00
Upstream Data
b9b0bff946 bug: pin betterproto version to avoid errors with unset oneof variants 2024-09-18 13:16:28 -06:00
Upstream Data
790718a5df bug: fix some issues with BOS+ calls. 2024-09-18 13:05:08 -06:00
Brett Rowan
96a0301f5e Merge pull request #203 from Ytemiloluwa/BTMiner
feat: Add _get_pools method for BTMiner(StockFirmware)
2024-09-17 11:30:14 -06:00
ytemiloluwa
c57b019b7d backends: add _get_pools to BTMiner 2024-09-17 08:55:09 +01:00
Brett Rowan
af920c4dda version: bump version number 2024-09-12 17:28:19 -06:00
Brett Rowan
f3d11788ed bug: fix missing await calls
Fixes #201
2024-09-12 17:27:57 -06:00
Brett Rowan
fd0e02af59 feature: add support for BOSMinerT21 2024-09-12 17:19:08 -06:00
Brett Rowan
2a6c51d52c Merge pull request #188 from Ytemiloluwa/marathon
feat: add _get_pools method for marathon miner
2024-09-04 08:38:29 -06:00
ytemiloluwa
2d62e2070b pool: highest priority 2024-09-04 15:21:39 +01:00
ytemiloluwa
b143bd70f0 updated keys 2024-09-03 22:49:55 +01:00
ytemiloluwa
605509c57c updated keys 2024-09-03 21:35:14 +01:00
Brett Rowan
7036137b23 Merge pull request #189 from 1e9abhi1e10/luxminer_firmware 2024-09-02 22:14:15 -06:00
1e9abhi1e10
7a9ff535b4 Refactor upgrade_firmware to maintain bool return type 2024-09-03 09:42:08 +05:30
1e9abhi1e10
0965e6489b return status message in upgrade_firmware function 2024-09-02 23:43:17 +05:30
ytemiloluwa
792e1c9cad corrected parsing 2024-09-02 09:27:30 +01:00
1e9abhi1e10
21636a75fa made upgrade_firmware to return boolean result 2024-08-23 12:45:20 +05:30
1e9abhi1e10
6fdd156fa3 Added upgraderun in rpc/luxminer 2024-08-21 01:06:50 +05:30
1e9abhi1e10
b957aa7fba feat: Add update firmware for LuxOS Miner 2024-08-20 16:12:33 +05:30
ytemiloluwa
a71aa6868a backends: add _get_pools to marathon 2024-08-19 07:08:22 +01:00
33 changed files with 1012 additions and 643 deletions

806
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -198,7 +198,7 @@ class MiningModePowerTune(MinerConfigValue):
def as_boser(self) -> dict:
cfg = {
"set_performance_mode": SetPerformanceModeRequest(
save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action=SaveAction.SAVE_AND_APPLY,
mode=PerformanceMode(
tuner_mode=TunerPerformanceMode(
power_target=PowerTargetMode(
@@ -275,7 +275,7 @@ class MiningModeHashrateTune(MinerConfigValue):
def as_boser(self) -> dict:
cfg = {
"set_performance_mode": SetPerformanceModeRequest(
save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action=SaveAction.SAVE_AND_APPLY,
mode=PerformanceMode(
tuner_mode=TunerPerformanceMode(
hashrate_target=HashrateTargetMode(

View File

@@ -467,7 +467,7 @@ class PoolConfig(MinerConfigValue):
def as_boser(self, user_suffix: str = None) -> dict:
return {
"set_pool_groups": SetPoolGroupsRequest(
save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action=SaveAction.SAVE_AND_APPLY,
pool_groups=[g.as_boser(user_suffix=user_suffix) for g in self.groups],
)
}

View File

@@ -25,7 +25,10 @@ class PoolUrl:
@classmethod
def from_str(cls, url: str) -> "PoolUrl":
parsed_url = urlparse(url)
scheme = Scheme(parsed_url.scheme)
if not parsed_url.scheme.strip() == "":
scheme = Scheme(parsed_url.scheme)
else:
scheme = Scheme.STRATUM_V1
host = parsed_url.hostname
port = parsed_url.port
pubkey = parsed_url.path.lstrip("/") if scheme == Scheme.STRATUM_V2 else None

View File

@@ -5,6 +5,7 @@ class AntminerModels(str, Enum):
D3 = "D3"
HS3 = "HS3"
L3Plus = "L3+"
KA3 = "KA3"
DR5 = "DR5"
L7 = "L7"
E9Pro = "E9Pro"
@@ -293,6 +294,7 @@ class AvalonminerModels(str, Enum):
class InnosiliconModels(str, Enum):
T3HPlus = "T3H+"
A10X = "A10X"
A11MX = "A11MX"
def __str__(self):
return self.value

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 AntminerModern
from pyasic.miners.device.models import KA3
class BMMinerKA3(AntminerModern, KA3):
pass

View File

@@ -14,4 +14,5 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .HS3 import BMMinerHS3
from .KA3 import BMMinerKA3
from .L3 import BMMinerL3Plus

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 BOSer
from pyasic.miners.device.models import T21
class BOSMinerT21(BOSer, T21):
pass

View File

@@ -15,3 +15,4 @@
# ------------------------------------------------------------------------------
from .S21 import BOSMinerS21
from .T21 import BOSMinerT21

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from typing import List, Optional, Union
from pathlib import Path
import logging
from pathlib import Path
from typing import List, Optional, Union
from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
@@ -141,17 +141,24 @@ class AntminerModern(BMMiner):
raise ValueError("File location must be provided for firmware upgrade.")
try:
result = await self.web.update_firmware(file=file, keep_settings=keep_settings)
result = await self.web.update_firmware(
file=file, keep_settings=keep_settings
)
if result.get("success"):
logging.info("Firmware upgrade process completed successfully for AntMiner.")
logging.info(
"Firmware upgrade process completed successfully for AntMiner."
)
return "Firmware upgrade completed successfully."
else:
error_message = result.get("message", "Unknown error")
logging.error(f"Firmware upgrade failed. Response: {error_message}")
return f"Firmware upgrade failed. Response: {error_message}"
except Exception as e:
logging.error(f"An error occurred during the firmware upgrade process: {e}", exc_info=True)
logging.error(
f"An error occurred during the firmware upgrade process: {e}",
exc_info=True,
)
raise
async def fault_light_on(self) -> bool:
@@ -367,7 +374,7 @@ class AntminerModern(BMMiner):
if web_get_conf is not None:
try:
if web_get_conf["bitmain-work-mode"].isdigit():
if str(web_get_conf["bitmain-work-mode"]).isdigit():
return (
False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
)

View File

@@ -22,6 +22,7 @@ from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.firmware import StockFirmware
from pyasic.rpc.bfgminer import BFGMinerRPCAPI
from pyasic.data.pools import PoolMetrics, PoolUrl
BFGMINER_DATA_LOC = DataLocations(
**{
@@ -49,6 +50,10 @@ BFGMINER_DATA_LOC = DataLocations(
"_get_fans",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_pools", "pools")],
),
}
)
@@ -207,6 +212,36 @@ class BFGMiner(StockFirmware):
return fans
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:
url = pool_info.get("URL")
pool_url = PoolUrl.from_str(url) if url else None
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_url,
user=pool_info.get("User"),
index=pool_info.get("POOL"),
)
pools_data.append(pool_data)
except LookupError:
pass
return pools_data
async def _get_expected_hashrate(
self, rpc_stats: dict = None
) -> Optional[AlgoHashRate]:

View File

@@ -41,6 +41,10 @@ BITAXE_DATA_LOC = DataLocations(
"_get_api_ver",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.MAC): DataFunction(
"_get_mac",
[WebAPICommand("web_system_info", "system/info")],
),
}
)
@@ -187,3 +191,16 @@ class BitAxe(BaseMiner):
return web_system_info["version"]
except KeyError:
pass
async def _get_mac(self, web_system_info: dict = None) -> Optional[str]:
if web_system_info is None:
try:
web_system_info = await self.web.system_info()
except APIError:
pass
if web_system_info is not None:
try:
return web_system_info["macAddr"].upper()
except KeyError:
pass

View File

@@ -21,6 +21,7 @@ from typing import List, Optional, Union
import aiofiles
import tomli_w
try:
import tomllib
except ImportError:
@@ -726,9 +727,8 @@ BOSER_DATA_LOC = DataLocations(
[RPCAPICommand("rpc_summary", "summary")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[WebAPICommand("grpc_pool_groups", "get_pool_groups")]
)
"_get_pools", [WebAPICommand("grpc_pool_groups", "get_pool_groups")]
),
}
)
@@ -798,7 +798,7 @@ class BOSer(BraiinsOSFirmware):
async def set_power_limit(self, wattage: int) -> bool:
try:
result = await self.web.set_power_target(
wattage, save_action=SaveAction.SAVE_ACTION_SAVE_AND_FORCE_APPLY
wattage, save_action=SaveAction.SAVE_AND_FORCE_APPLY
)
except APIError:
return False
@@ -926,8 +926,10 @@ class BOSer(BraiinsOSFirmware):
pass
if grpc_hashboards is not None:
for board in grpc_hashboards["hashboards"]:
idx = int(board["id"]) - 1
grpc_boards = sorted(
grpc_hashboards["hashboards"], key=lambda x: int(x["id"])
)
for idx, board in enumerate(grpc_boards):
if board.get("chipsCount") is not None:
hashboards[idx].chips = board["chipsCount"]
if board.get("boardTemp") is not None:
@@ -951,7 +953,7 @@ class BOSer(BraiinsOSFirmware):
async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
if grpc_miner_stats is None:
try:
grpc_miner_stats = self.web.get_miner_stats()
grpc_miner_stats = await self.web.get_miner_stats()
except APIError:
pass
@@ -983,7 +985,7 @@ class BOSer(BraiinsOSFirmware):
async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]:
if grpc_cooling_state is None:
try:
grpc_cooling_state = self.web.get_cooling_state()
grpc_cooling_state = await self.web.get_cooling_state()
except APIError:
pass
@@ -1086,12 +1088,12 @@ class BOSer(BraiinsOSFirmware):
url=pool_info["url"],
user=pool_info["user"],
index=idx,
accepted=pool_info["stats"]["acceptedShares"],
rejected=pool_info["stats"]["rejectedShares"],
get_failures=pool_info["stats"]["stale_shares"],
accepted=pool_info["stats"].get("acceptedShares", 0),
rejected=pool_info["stats"].get("rejectedShares", 0),
get_failures=0,
remote_failures=0,
active=pool_info["active"],
alive=pool_info["alive"]
active=pool_info.get("active", False),
alive=pool_info.get("alive"),
)
pools_data.append(pool_data)

View File

@@ -27,6 +27,7 @@ from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.firmware import StockFirmware
from pyasic.rpc.btminer import BTMinerRPCAPI
from pyasic.data.pools import PoolMetrics, PoolUrl
BTMINER_DATA_LOC = DataLocations(
**{
@@ -109,6 +110,10 @@ BTMINER_DATA_LOC = DataLocations(
"_get_uptime",
[RPCAPICommand("rpc_summary", "summary")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_pools", "pools")],
)
}
)
@@ -655,6 +660,36 @@ class BTMiner(StockFirmware):
except LookupError:
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:
url = pool_info.get("URL")
pool_url = PoolUrl.from_str(url) if url else None
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_url,
user=pool_info.get("User"),
index=pool_info.get("POOL"),
)
pools_data.append(pool_data)
except LookupError:
pass
return pools_data
async def upgrade_firmware(self, file: Path):
"""
Upgrade the firmware of the Whatsminer device.

View File

@@ -62,6 +62,10 @@ GOLDSHELL_DATA_LOC = DataLocations(
"_get_fans",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_pools", "pools")],
),
}
)

View File

@@ -14,6 +14,7 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from typing import List, Optional
import logging
from pyasic.config import MinerConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
@@ -146,6 +147,22 @@ class LUXMiner(LuxOSFirmware):
async def get_config(self) -> MinerConfig:
return self.config
async def upgrade_firmware(self) -> bool:
"""
Upgrade the firmware on a LuxOS miner by calling the 'updaterun' API command.
Returns:
bool: True if the firmware upgrade was successfully initiated, False otherwise.
"""
try:
await self.rpc.upgraderun()
logging.info(f"{self.ip}: Firmware upgrade initiated successfully.")
return True
except APIError as e:
logging.error(f"{self.ip}: Firmware upgrade failed: {e}")
return False
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################

View File

@@ -9,6 +9,7 @@ from pyasic.miners.device.firmware import MaraFirmware
from pyasic.misc import merge_dicts
from pyasic.rpc.marathon import MaraRPCAPI
from pyasic.web.marathon import MaraWebAPI
from pyasic.data.pools import PoolMetrics, PoolUrl
MARA_DATA_LOC = DataLocations(
**{
@@ -60,6 +61,10 @@ MARA_DATA_LOC = DataLocations(
"_get_uptime",
[WebAPICommand("web_brief", "brief")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[WebAPICommand("web_pools", "pools")],
),
}
)
@@ -305,3 +310,40 @@ class MaraMiner(MaraFirmware):
return web_miner_config["mode"]["concorde"]["power-target"]
except LookupError:
pass
async def _get_pools(self, web_pools: list = None) -> List[PoolMetrics]:
if web_pools is None:
try:
web_pools = await self.web.pools()
except APIError:
return []
active_pool_index = None
highest_priority = float('inf')
for pool_info in web_pools:
if pool_info.get("status") == "Alive" and pool_info.get("priority", float('inf')) < highest_priority:
highest_priority = pool_info.get["priority"]
active_pool_index = pool_info["index"]
pools_data = []
if web_pools is not None:
try:
for pool_info in web_pools:
url = pool_info.get("url")
pool_url = PoolUrl.from_str(url) if url else None
pool_data = PoolMetrics(
accepted=pool_info.get("accepted"),
rejected=pool_info.get("rejected"),
get_failures=pool_info.get("stale"),
remote_failures=pool_info.get("discarded"),
active=pool_info.get("index") == active_pool_index,
alive=pool_info.get("status") == "Alive",
url=pool_url,
user=pool_info.get("user"),
index=pool_info.get("index"),
)
pools_data.append(pool_data)
except LookupError:
pass
return pools_data

View File

@@ -0,0 +1,23 @@
# ------------------------------------------------------------------------------
# Copyright 2022 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.device.models import MinerModel
from pyasic.miners.device.makes import AntMinerMake
class KA3(AntMinerMake):
raw_model = MinerModel.ANTMINER.KA3
expected_chips = 92

View File

@@ -15,4 +15,5 @@
# ------------------------------------------------------------------------------
from .D3 import D3
from .HS3 import HS3
from .KA3 import KA3
from .L3 import L3Plus

View File

@@ -0,0 +1,21 @@
# ------------------------------------------------------------------------------
# Copyright 2022 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.device.models import MinerModel
from pyasic.miners.device.makes import InnosiliconMake
class A11MX(InnosiliconMake):
raw_model = MinerModel.INNOSILICON.A11MX

View File

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

View File

@@ -15,4 +15,5 @@
# ------------------------------------------------------------------------------
from .A10X import *
from .A11X import *
from .T3X import *

View File

@@ -66,6 +66,7 @@ MINER_CLASSES = {
"ANTMINER D3": CGMinerD3,
"ANTMINER HS3": BMMinerHS3,
"ANTMINER L3+": BMMinerL3Plus,
"ANTMINER KA3": BMMinerKA3,
"ANTMINER DR5": CGMinerDR5,
"ANTMINER L7": BMMinerL7,
"ANTMINER E9 PRO": BMMinerE9Pro,
@@ -338,6 +339,7 @@ MINER_CLASSES = {
None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}),
"T3H+": InnosiliconT3HPlus,
"A10X": InnosiliconA10X,
"A11MX": InnosiliconA11MX,
},
MinerTypes.GOLDSHELL: {
None: type("GoldshellUnknown", (GoldshellMiner, GoldshellMake), {}),
@@ -376,6 +378,7 @@ MINER_CLASSES = {
"ANTMINER S19 PRO+ HYD.": BOSMinerS19ProPlusHydro,
"ANTMINER T19": BOSMinerT19,
"ANTMINER S21": BOSMinerS21,
"ANTMINER T21": BOSMinerT21,
},
MinerTypes.VNISH: {
None: VNish,
@@ -712,11 +715,13 @@ class MinerFactory:
or "BFGMINER" in upper_data
):
return MinerTypes.GOLDSHELL
if "INNOMINER" in upper_data:
return MinerTypes.INNOSILICON
if "AVALON" in upper_data:
return MinerTypes.AVALONMINER
if "GCMINER" in upper_data or "FLUXOS" in upper_data:
return MinerTypes.AURADINE
if "VNISH" in upper_data or "DEVICE PATH" in upper_data:
if "VNISH" in upper_data:
return MinerTypes.VNISH
async def send_web_command(

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.innosilicon import Innosilicon
from pyasic.miners.device.models import A11MX
class InnosiliconA11MX(Innosilicon, A11MX):
pass

View File

@@ -0,0 +1 @@
from .A11M import InnosiliconA11MX

View File

@@ -15,4 +15,5 @@
# ------------------------------------------------------------------------------
from .A10X import *
from .A11X import *
from .T3X import *

View File

@@ -268,12 +268,8 @@ If you are sure you want to use this command please use API.send_command("{comma
# fix an error with a btminer return having a missing comma. (2023-01-06 version)
str_data = str_data.replace('""temp0', '","temp0')
# fix an error with Avalonminers returning inf and nan
str_data = str_data.replace("info", "1nfo")
str_data = str_data.replace("inf", "0")
str_data = str_data.replace("1nfo", "info")
str_data = str_data.replace("nano", "n4no")
str_data = str_data.replace("nan", "0")
str_data = str_data.replace("n4no", "nano")
str_data = str_data.replace('"inf"', "0")
str_data = str_data.replace('"nan"', "0")
# fix whatever this garbage from avalonminers is `,"id":1}`
if str_data.startswith(","):

View File

@@ -749,3 +749,12 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
</details>
"""
return await self.send_command("wakeup", parameters=session_id)
async def upgraderun(self):
"""
Send the 'updaterun' command to the miner.
Returns:
The response from the miner after sending the 'updaterun' command.
"""
return await self.send_command("updaterun")

View File

@@ -0,0 +1,93 @@
from datetime import datetime, timedelta
from typing import Any, Dict
from betterproto import DATETIME_ZERO, TYPE_MAP, TYPE_MESSAGE, Casing, Message
# https://github.com/danielgtaylor/python-betterproto/pull/609
def to_pydict(
self, casing: Casing = Casing.CAMEL, include_default_values: bool = False
) -> Dict[str, Any]:
"""
Returns a python dict representation of this object.
Parameters
-----------
casing: :class:`Casing`
The casing to use for key values. Default is :attr:`Casing.CAMEL` for
compatibility purposes.
include_default_values: :class:`bool`
If ``True`` will include the default values of fields. Default is ``False``.
E.g. an ``int32`` field will be included with a value of ``0`` if this is
set to ``True``, otherwise this would be ignored.
Returns
--------
Dict[:class:`str`, Any]
The python dict representation of this object.
"""
output: Dict[str, Any] = {}
defaults = self._betterproto.default_gen
for field_name, meta in self._betterproto.meta_by_field_name.items():
field_is_repeated = defaults[field_name] is list
try:
value = getattr(self, field_name)
except AttributeError:
value = self._get_field_default(field_name)
cased_name = casing(field_name).rstrip("_") # type: ignore
if meta.proto_type == TYPE_MESSAGE:
if isinstance(value, datetime):
if (
value != DATETIME_ZERO
or include_default_values
or self._include_default_value_for_oneof(
field_name=field_name, meta=meta
)
):
output[cased_name] = value
elif isinstance(value, timedelta):
if (
value != timedelta(0)
or include_default_values
or self._include_default_value_for_oneof(
field_name=field_name, meta=meta
)
):
output[cased_name] = value
elif meta.wraps:
if value is not None or include_default_values:
output[cased_name] = value
elif field_is_repeated:
# Convert each item.
value = [i.to_pydict(casing, include_default_values) for i in value]
if value or include_default_values:
output[cased_name] = value
elif value is None:
if include_default_values:
output[cased_name] = None
elif (
value._serialized_on_wire
or include_default_values
or self._include_default_value_for_oneof(
field_name=field_name, meta=meta
)
):
output[cased_name] = value.to_pydict(casing, include_default_values)
elif meta.proto_type == TYPE_MAP:
for k in value:
if hasattr(value[k], "to_pydict"):
value[k] = value[k].to_pydict(casing, include_default_values)
if value or include_default_values:
output[cased_name] = value
elif (
value != self._get_field_default(field_name)
or include_default_values
or self._include_default_value_for_oneof(field_name=field_name, meta=meta)
):
output[cased_name] = value
return output
def patch():
Message.to_pydict = to_pydict

View File

@@ -26,6 +26,9 @@ from grpclib.client import Channel
from pyasic import settings
from pyasic.errors import APIError
from pyasic.web.base import BaseWebAPI
from pyasic.web.braiins_os.better_monkey import patch
patch()
from .proto.braiins.bos import *
from .proto.braiins.bos.v1 import *
@@ -206,7 +209,7 @@ class BOSerWebAPI(BaseWebAPI):
async def set_immersion_mode(
self,
enable: bool,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
) -> dict:
return await self.send_command(
"set_immersion_mode",
@@ -227,7 +230,7 @@ class BOSerWebAPI(BaseWebAPI):
)
async def set_default_power_target(
self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY
self, save_action: SaveAction = SaveAction.SAVE_AND_APPLY
) -> dict:
return await self.send_command(
"set_default_power_target",
@@ -238,7 +241,7 @@ class BOSerWebAPI(BaseWebAPI):
async def set_power_target(
self,
power_target: int,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
) -> dict:
return await self.send_command(
"set_power_target",
@@ -251,7 +254,7 @@ class BOSerWebAPI(BaseWebAPI):
async def increment_power_target(
self,
power_target_increment: int,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
) -> dict:
return await self.send_command(
"increment_power_target",
@@ -265,7 +268,7 @@ class BOSerWebAPI(BaseWebAPI):
async def decrement_power_target(
self,
power_target_decrement: int,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
) -> dict:
return await self.send_command(
"decrement_power_target",
@@ -277,7 +280,7 @@ class BOSerWebAPI(BaseWebAPI):
)
async def set_default_hashrate_target(
self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY
self, save_action: SaveAction = SaveAction.SAVE_AND_APPLY
) -> dict:
return await self.send_command(
"set_default_hashrate_target",
@@ -288,7 +291,7 @@ class BOSerWebAPI(BaseWebAPI):
async def set_hashrate_target(
self,
hashrate_target: float,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
) -> dict:
return await self.send_command(
"set_hashrate_target",
@@ -302,7 +305,7 @@ class BOSerWebAPI(BaseWebAPI):
async def increment_hashrate_target(
self,
hashrate_target_increment: int,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
) -> dict:
return await self.send_command(
"increment_hashrate_target",
@@ -318,7 +321,7 @@ class BOSerWebAPI(BaseWebAPI):
async def decrement_hashrate_target(
self,
hashrate_target_decrement: int,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
) -> dict:
return await self.send_command(
"decrement_hashrate_target",
@@ -359,7 +362,7 @@ class BOSerWebAPI(BaseWebAPI):
self,
wattage_target: int = None,
hashrate_target: int = None,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
) -> dict:
if wattage_target is not None and hashrate_target is not None:
logging.error(
@@ -459,7 +462,7 @@ class BOSerWebAPI(BaseWebAPI):
async def enable_hashboards(
self,
hashboard_ids: List[str],
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
) -> dict:
return await self.send_command(
"enable_hashboards",
@@ -472,7 +475,7 @@ class BOSerWebAPI(BaseWebAPI):
async def disable_hashboards(
self,
hashboard_ids: List[str],
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
) -> dict:
return await self.send_command(
"disable_hashboards",
@@ -485,7 +488,7 @@ class BOSerWebAPI(BaseWebAPI):
async def set_pool_groups(
self,
pool_groups: List[PoolGroupConfiguration],
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
save_action: SaveAction = SaveAction.SAVE_AND_APPLY,
) -> dict:
return await self.send_command(
"set_pool_groups",

View File

@@ -4,12 +4,17 @@
# This file has been @generated
from dataclasses import dataclass
from typing import TYPE_CHECKING, Dict, Optional
from typing import (
TYPE_CHECKING,
Dict,
Optional,
)
import betterproto
import grpclib
from betterproto.grpc.grpclib_server import ServiceBase
if TYPE_CHECKING:
import grpclib.server
from betterproto.grpc.grpclib_client import MetadataLike
@@ -18,7 +23,7 @@ if TYPE_CHECKING:
@dataclass(eq=False, repr=False)
class ApiVersion(betterproto.Message):
"""LATEST_API_VERSION=1.2.0"""
"""LATEST_API_VERSION=1.3.0"""
major: int = betterproto.uint64_field(1)
minor: int = betterproto.uint64_field(2)

View File

@@ -5,12 +5,19 @@
import warnings
from dataclasses import dataclass
from datetime import datetime
from typing import TYPE_CHECKING, AsyncIterator, Dict, List, Optional
from typing import (
TYPE_CHECKING,
AsyncIterator,
Dict,
List,
Optional,
)
import betterproto
import grpclib
from betterproto.grpc.grpclib_server import ServiceBase
if TYPE_CHECKING:
import grpclib.server
from betterproto.grpc.grpclib_client import MetadataLike
@@ -20,125 +27,129 @@ if TYPE_CHECKING:
class SaveAction(betterproto.Enum):
"""Save action for different operations"""
SAVE_ACTION_UNSPECIFIED = 0
SAVE_ACTION_SAVE = 1
SAVE_ACTION_SAVE_AND_APPLY = 2
SAVE_ACTION_SAVE_AND_FORCE_APPLY = 3
UNSPECIFIED = 0
SAVE = 1
SAVE_AND_APPLY = 2
SAVE_AND_FORCE_APPLY = 3
class CoolingMode(betterproto.Enum):
COOLING_MODE_UNSPECIFIED = 0
COOLING_MODE_AUTO = 1
COOLING_MODE_MANUAL = 2
COOLING_MODE_DISABLED = 3
UNSPECIFIED = 0
AUTO = 1
MANUAL = 2
DISABLED = 3
class SensorLocation(betterproto.Enum):
SENSOR_LOCATION_UNSPECIFIED = 0
SENSOR_LOCATION_CHIP = 1
SENSOR_LOCATION_PCB = 2
UNSPECIFIED = 0
CHIP = 1
PCB = 2
class TunerMode(betterproto.Enum):
TUNER_MODE_UNSPECIFIED = 0
TUNER_MODE_POWER_TARGET = 1
TUNER_MODE_HASHRATE_TARGET = 2
UNSPECIFIED = 0
POWER_TARGET = 1
HASHRATE_TARGET = 2
class TunerState(betterproto.Enum):
TUNER_STATE_UNSPECIFIED = 0
TUNER_STATE_DISABLED = 1
TUNER_STATE_STABLE = 2
TUNER_STATE_TUNING = 3
TUNER_STATE_ERROR = 4
UNSPECIFIED = 0
DISABLED = 1
STABLE = 2
TUNING = 3
ERROR = 4
class LicenseType(betterproto.Enum):
LICENSE_TYPE_UNSPECIFIED = 0
LICENSE_TYPE_STANDARD = 1
LICENSE_TYPE_CUSTOM = 2
UNSPECIFIED = 0
STANDARD = 1
CUSTOM = 2
class Platform(betterproto.Enum):
"""Supported platforms"""
PLATFORM_UNSPECIFIED = 0
PLATFORM_AM1_S9 = 1
PLATFORM_AM2_S17 = 2
PLATFORM_AM3_BBB = 3
PLATFORM_AM3_AML = 4
PLATFORM_STM32MP157C_II1_AM2 = 5
PLATFORM_CVITEK_BM1_AM2 = 6
PLATFORM_ZYNQ_BM3_AM2 = 7
UNSPECIFIED = 0
AM1_S9 = 1
AM2_S17 = 2
AM3_BBB = 3
AM3_AML = 4
STM32MP157C_II1_AM2 = 5
CVITEK_BM1_AM2 = 6
ZYNQ_BM3_AM2 = 7
STM32MP157C_II2_BMM1 = 8
class BosMode(betterproto.Enum):
"""BOS modes enumeration"""
BOS_MODE_UNSPECIFIED = 0
BOS_MODE_UPGRADE = 1
BOS_MODE_RECOVERY = 2
BOS_MODE_SD = 3
BOS_MODE_NAND = 4
BOS_MODE_EMMC = 5
UNSPECIFIED = 0
UPGRADE = 1
RECOVERY = 2
SD = 3
NAND = 4
EMMC = 5
class MinerBrand(betterproto.Enum):
MINER_BRAND_UNSPECIFIED = 0
MINER_BRAND_ANTMINER = 1
MINER_BRAND_WHATSMINER = 2
UNSPECIFIED = 0
ANTMINER = 1
WHATSMINER = 2
class MinerModel(betterproto.Enum):
"""Deprecated: This enumeration is not longer maintained"""
MINER_MODEL_UNSPECIFIED = 0
MINER_MODEL_ANTMINER_S9 = 1
MINER_MODEL_ANTMINER_X17 = 2
MINER_MODEL_ANTMINER_S17 = 3
MINER_MODEL_ANTMINER_S17_PLUS = 4
MINER_MODEL_ANTMINER_S17_PRO = 5
MINER_MODEL_ANTMINER_S17E = 6
MINER_MODEL_ANTMINER_T17 = 7
MINER_MODEL_ANTMINER_T17E = 8
MINER_MODEL_ANTMINER_T17_PLUS = 9
MINER_MODEL_ANTMINER_X19 = 10
MINER_MODEL_ANTMINER_S19 = 11
MINER_MODEL_ANTMINER_S19_PRO = 12
MINER_MODEL_ANTMINER_S19_PLUS = 13
MINER_MODEL_ANTMINER_S19J = 14
MINER_MODEL_ANTMINER_S19J_PRO = 15
MINER_MODEL_ANTMINER_S19A = 16
MINER_MODEL_ANTMINER_S19A_PRO = 17
MINER_MODEL_ANTMINER_S19XP = 18
MINER_MODEL_ANTMINER_T19 = 19
MINER_MODEL_ANTMINER_S19J_PRO_PLUS = 20
UNSPECIFIED = 0
ANTMINER_S9 = 1
ANTMINER_X17 = 2
ANTMINER_S17 = 3
ANTMINER_S17_PLUS = 4
ANTMINER_S17_PRO = 5
ANTMINER_S17E = 6
ANTMINER_T17 = 7
ANTMINER_T17E = 8
ANTMINER_T17_PLUS = 9
ANTMINER_X19 = 10
ANTMINER_S19 = 11
ANTMINER_S19_PRO = 12
ANTMINER_S19_PLUS = 13
ANTMINER_S19J = 14
ANTMINER_S19J_PRO = 15
ANTMINER_S19A = 16
ANTMINER_S19A_PRO = 17
ANTMINER_S19XP = 18
ANTMINER_T19 = 19
ANTMINER_S19J_PRO_PLUS = 20
class MinerStatus(betterproto.Enum):
MINER_STATUS_UNSPECIFIED = 0
MINER_STATUS_NOT_STARTED = 1
MINER_STATUS_NORMAL = 2
MINER_STATUS_PAUSED = 3
MINER_STATUS_SUSPENDED = 4
MINER_STATUS_RESTRICTED = 5
UNSPECIFIED = 0
NOT_STARTED = 1
NORMAL = 2
PAUSED = 3
SUSPENDED = 4
RESTRICTED = 5
class SupportArchiveFormat(betterproto.Enum):
"""Enumeration for support archive format"""
SUPPORT_ARCHIVE_FORMAT_UNSPECIFIED = 0
SUPPORT_ARCHIVE_FORMAT_ZIP = 1
UNSPECIFIED = 0
ZIP = 1
"""Compressed zip format"""
SUPPORT_ARCHIVE_FORMAT_BOS = 2
BOS = 2
"""BOS custom format"""
ZIP_ENCRYPTED = 3
"""Compressed encrypted zip format"""
class NetworkProtocol(betterproto.Enum):
NETWORK_PROTOCOL_UNSPECIFIED = 0
NETWORK_PROTOCOL_DHCP = 1
NETWORK_PROTOCOL_STATIC = 2
UNSPECIFIED = 0
DHCP = 1
STATIC = 2
@dataclass(eq=False, repr=False)
@@ -268,8 +279,8 @@ class LoginResponse(betterproto.Message):
timeout_s: int = betterproto.uint32_field(2)
"""
Authentication token validity/timeout in seconds. Token validity refreshed
to this value with each request.
Authentication token validity/timeout in seconds.
Token validity refreshed to this value with each request.
"""
@@ -277,9 +288,7 @@ class LoginResponse(betterproto.Message):
class SetPasswordRequest(betterproto.Message):
"""Request for set password action."""
password: Optional[str] = betterproto.string_field(
1, optional=True, group="_password"
)
password: Optional[str] = betterproto.string_field(1, optional=True)
@dataclass(eq=False, repr=False)
@@ -335,8 +344,8 @@ class BasesPoints(betterproto.Message):
bsp: int = betterproto.uint32_field(1)
"""
A basis point is one hundredth of 1 percentage point. For example: 1bps =
0.01%, 250bps = 2.5%
A basis point is one hundredth of 1 percentage point.
For example: 1bps = 0.01%, 250bps = 2.5%
"""
@@ -409,9 +418,9 @@ class VoltageConstraints(betterproto.Message):
@dataclass(eq=False, repr=False)
class CoolingAutoMode(betterproto.Message):
"""
The temperature control modes. Miner software tries to regulate the fan
speed so that miner temperature is approximately at the target temperature.
The allowed temperature range is 0-200 degree Celsius.
The temperature control modes.
Miner software tries to regulate the fan speed so that miner temperature is approximately at the target temperature.
The allowed temperature range is 0-200 degree Celsius.
"""
target_temperature: "Temperature" = betterproto.message_field(1)
@@ -422,8 +431,7 @@ class CoolingAutoMode(betterproto.Message):
dangerous_temperature: "Temperature" = betterproto.message_field(3)
"""
Temperature threshold at which BOSMiner shuts down in order to prevent
overheating and damaging the miner.
Temperature threshold at which BOSMiner shuts down in order to prevent overheating and damaging the miner.
"""
@@ -433,12 +441,11 @@ class CoolingManualMode(betterproto.Message):
Fans are kept at a fixed, user-defined speed, no matter the temperature.
"""
fan_speed_ratio: Optional[float] = betterproto.double_field(
1, optional=True, group="_fan_speed_ratio"
)
fan_speed_ratio: Optional[float] = betterproto.double_field(1, optional=True)
"""
User defined fan speed expressed as a ratio between 0.0 and 1.0 where 0.0
means completely turned off and 1.0 means running at full speed possible
User defined fan speed expressed as a ratio between 0.0 and 1.0
where 0.0 means completely turned off and
1.0 means running at full speed possible
"""
hot_temperature: "Temperature" = betterproto.message_field(2)
@@ -446,8 +453,7 @@ class CoolingManualMode(betterproto.Message):
dangerous_temperature: "Temperature" = betterproto.message_field(3)
"""
Temperature threshold at which BOSMiner shuts down in order to prevent
overheating and damaging the miner.
Temperature threshold at which BOSMiner shuts down in order to prevent overheating and damaging the miner.
"""
@@ -455,20 +461,17 @@ class CoolingManualMode(betterproto.Message):
class CoolingDisabledMode(betterproto.Message):
"""Disable temperature control. May be dangerous."""
fan_speed_ratio: Optional[float] = betterproto.double_field(
1, optional=True, group="_fan_speed_ratio"
)
fan_speed_ratio: Optional[float] = betterproto.double_field(1, optional=True)
"""
User defined fan speed expressed as a ratio between 0.0 and 1.0 where 0.0
means completely turned off and 1.0 means running at full speed possible
User defined fan speed expressed as a ratio between 0.0 and 1.0
where 0.0 means completely turned off and
1.0 means running at full speed possible
"""
@dataclass(eq=False, repr=False)
class CoolingConfiguration(betterproto.Message):
minimum_required_fans: Optional[int] = betterproto.uint32_field(
1, optional=True, group="_minimum_required_fans"
)
minimum_required_fans: Optional[int] = betterproto.uint32_field(1, optional=True)
auto: "CoolingAutoMode" = betterproto.message_field(2, group="mode")
manual: "CoolingManualMode" = betterproto.message_field(3, group="mode")
disabled: "CoolingDisabledMode" = betterproto.message_field(4, group="mode")
@@ -488,23 +491,19 @@ class CoolingConstraints(betterproto.Message):
class FanState(betterproto.Message):
"""Structure which contain info about one specific miner fan."""
position: Optional[int] = betterproto.uint32_field(
1, optional=True, group="_position"
)
position: Optional[int] = betterproto.uint32_field(1, optional=True)
"""Fan positions/ID"""
rpm: int = betterproto.uint32_field(2)
"""Actual fan RPM (Revolutions/Rotation Per Minute)"""
target_speed_ratio: Optional[float] = betterproto.double_field(
3, optional=True, group="_target_speed_ratio"
)
target_speed_ratio: Optional[float] = betterproto.double_field(3, optional=True)
"""Actual fan speed ratio(PWM) in range 0.0 - 1.0"""
@dataclass(eq=False, repr=False)
class TemperatureSensor(betterproto.Message):
id: Optional[int] = betterproto.uint32_field(1, optional=True, group="_id")
id: Optional[int] = betterproto.uint32_field(1, optional=True)
"""Sensor id"""
location: "SensorLocation" = betterproto.enum_field(2)
@@ -523,7 +522,10 @@ class GetCoolingStateRequest(betterproto.Message):
@dataclass(eq=False, repr=False)
class GetCoolingStateResponse(betterproto.Message):
"""Response to get current fan states and temperature measurements"""
"""
Response to get current fan states and
temperature measurements
"""
fans: List["FanState"] = betterproto.message_field(1)
"""All Fans state"""
@@ -551,12 +553,10 @@ class SetImmersionModeResponse(betterproto.Message):
@dataclass(eq=False, repr=False)
class TunerConfiguration(betterproto.Message):
enabled: Optional[bool] = betterproto.bool_field(1, optional=True, group="_enabled")
enabled: Optional[bool] = betterproto.bool_field(1, optional=True)
"""Flag if tuner is enabled"""
tuner_mode: Optional["TunerMode"] = betterproto.enum_field(
2, optional=True, group="_tuner_mode"
)
tuner_mode: Optional["TunerMode"] = betterproto.enum_field(2, optional=True)
"""Tuner mode"""
power_target: "Power" = betterproto.message_field(3)
@@ -583,7 +583,7 @@ class TunerConstraints(betterproto.Message):
@dataclass(eq=False, repr=False)
class DpsConfiguration(betterproto.Message):
enabled: Optional[bool] = betterproto.bool_field(1, optional=True, group="_enabled")
enabled: Optional[bool] = betterproto.bool_field(1, optional=True)
"""Flag if Dynamic Performance Scaling is enabled"""
power_step: "Power" = betterproto.message_field(2)
@@ -598,9 +598,7 @@ class DpsConfiguration(betterproto.Message):
min_hashrate_target: "TeraHashrate" = betterproto.message_field(5)
"""Dynamic Performance Scaling minimal hashrate target"""
shutdown_enabled: Optional[bool] = betterproto.bool_field(
6, optional=True, group="_shutdown_enabled"
)
shutdown_enabled: Optional[bool] = betterproto.bool_field(6, optional=True)
"""Flag if shutdown for Dynamic Performance Scaling is enabled"""
shutdown_duration: "Hours" = betterproto.message_field(7)
@@ -879,17 +877,13 @@ class SetDpsRequest(betterproto.Message):
save_action: "SaveAction" = betterproto.enum_field(1)
"""Save action"""
enable: Optional[bool] = betterproto.bool_field(2, optional=True, group="_enable")
enable: Optional[bool] = betterproto.bool_field(2, optional=True)
"""Flag if Dynamic Performance Scaling should be enabled"""
enable_shutdown: Optional[bool] = betterproto.bool_field(
3, optional=True, group="_enable_shutdown"
)
enable_shutdown: Optional[bool] = betterproto.bool_field(3, optional=True)
"""Flag if shutdown for Dynamic Performance Scaling should be enabled"""
shutdown_duration: Optional["Hours"] = betterproto.message_field(
4, optional=True, group="_shutdown_duration"
)
shutdown_duration: Optional["Hours"] = betterproto.message_field(4, optional=True)
"""Dynamic Performance Scaling shutdown duration"""
target: "DpsTarget" = betterproto.message_field(5)
@@ -898,17 +892,13 @@ class SetDpsRequest(betterproto.Message):
@dataclass(eq=False, repr=False)
class SetDpsResponse(betterproto.Message):
enabled: Optional[bool] = betterproto.bool_field(1, optional=True, group="_enabled")
enabled: Optional[bool] = betterproto.bool_field(1, optional=True)
"""Flag if Dynamic Performance Scaling is enabled"""
shutdown_enabled: Optional[bool] = betterproto.bool_field(
2, optional=True, group="_shutdown_enabled"
)
shutdown_enabled: Optional[bool] = betterproto.bool_field(2, optional=True)
"""Flag if shutdown for Dynamic Performance Scaling should be enabled"""
shutdown_duration: Optional["Hours"] = betterproto.message_field(
3, optional=True, group="_shutdown_duration"
)
shutdown_duration: Optional["Hours"] = betterproto.message_field(3, optional=True)
"""Dynamic Performance Scaling shutdown duration"""
power_target: "DpsPowerTarget" = betterproto.message_field(4)
@@ -935,7 +925,7 @@ class HashboardConfig(betterproto.Message):
id: str = betterproto.string_field(1)
"""Hashboard id"""
enabled: Optional[bool] = betterproto.bool_field(2, optional=True, group="_enabled")
enabled: Optional[bool] = betterproto.bool_field(2, optional=True)
"""Flag if HB si enabled"""
frequency: "Frequency" = betterproto.message_field(3)
@@ -1019,9 +1009,9 @@ class Quota(betterproto.Message):
@dataclass(eq=False, repr=False)
class FixedShareRatio(betterproto.Message):
"""
Structure for fixed share ratio load balance strategy Fixed share ratio is
value between 0.0 to 1.0 where 1.0 represents that all work is generated
from the group
Structure for fixed share ratio load balance strategy
Fixed share ratio is value between 0.0 to 1.0 where 1.0 represents that all work is
generated from the group
"""
value: float = betterproto.double_field(1)
@@ -1058,12 +1048,10 @@ class PoolConfiguration(betterproto.Message):
user: str = betterproto.string_field(3)
"""Pool connection user"""
password: Optional[str] = betterproto.string_field(
4, optional=True, group="_password"
)
password: Optional[str] = betterproto.string_field(4, optional=True)
"""Pool connection password if set"""
enabled: Optional[bool] = betterproto.bool_field(5, optional=True, group="_enabled")
enabled: Optional[bool] = betterproto.bool_field(5, optional=True)
"""Flag if pool connection is enabled"""
@@ -1130,9 +1118,7 @@ class PoolStats(betterproto.Message):
generated_work: int = betterproto.uint64_field(6)
"""Generated work"""
last_share_time: Optional[datetime] = betterproto.message_field(
7, optional=True, group="_last_share_time"
)
last_share_time: Optional[datetime] = betterproto.message_field(7, optional=True)
"""Last share time"""
@@ -1154,9 +1140,9 @@ class GetPoolGroupsResponse(betterproto.Message):
@dataclass(eq=False, repr=False)
class CreatePoolGroupRequest(betterproto.Message):
"""
Request for pool group create action group.uid must not be specified (it
will be generated) group.pools[].uid must not be specified (it will be
generated)
Request for pool group create action
group.uid must not be specified (it will be generated)
group.pools[].uid must not be specified (it will be generated)
"""
save_action: "SaveAction" = betterproto.enum_field(1)
@@ -1177,9 +1163,9 @@ class CreatePoolGroupResponse(betterproto.Message):
@dataclass(eq=False, repr=False)
class UpdatePoolGroupRequest(betterproto.Message):
"""
Request for pool group update action group.uid must be specified and
represents unique id of group which will be updated group.pools[].uid must
not be specified (it will be generated)
Request for pool group update action
group.uid must be specified and represents unique id of group which will be updated
group.pools[].uid must not be specified (it will be generated)
"""
save_action: "SaveAction" = betterproto.enum_field(1)
@@ -1221,16 +1207,15 @@ class SetPoolGroupsRequest(betterproto.Message):
save_action: "SaveAction" = betterproto.enum_field(1)
"""
Save action SAVE just update config but changes will not be applied
SAVE_AND_APPLY and SAVE_AND_FORCE_APPLY are equal for this method. Pools
config will be updated and changes will be applied that will trigger
restart.
Save action
SAVE just update config but changes will not be applied
SAVE_AND_APPLY and SAVE_AND_FORCE_APPLY are equal for this method. Pools config will be updated and changes will be applied that will trigger restart.
"""
pool_groups: List["PoolGroupConfiguration"] = betterproto.message_field(2)
"""
Pool groups configuration `uid` must not be specified (it will be
generated)
Pool groups configuration
`uid` must not be specified (it will be generated)
"""
@@ -1292,9 +1277,8 @@ class NoneLicense(betterproto.Message):
time_to_restricted: int = betterproto.uint32_field(1)
"""
BOS Initialization timeout - number of seconds elapsed since bosminer start
i.e., number of seconds BOS will start mining in restricted mode burning 5%
of hashrate For more, see Section 3.10 of
https://braiins.com/os/plus/license
i.e., number of seconds BOS will start mining in restricted mode burning 5% of hashrate
For more, see Section 3.10 of https://braiins.com/os/plus/license
"""
@@ -1313,9 +1297,8 @@ class ValidLicense(betterproto.Message):
time_to_restricted: int = betterproto.uint32_field(3)
"""
Current license expiration - number of seconds since the moment the license
was received i.e., number of seconds BOS will start mining in restricted
mode burning 15% of hashrate
Current license expiration - number of seconds since the moment the license was received
i.e., number of seconds BOS will start mining in restricted mode burning 15% of hashrate
"""
dev_fee: "BasesPoints" = betterproto.message_field(4)
@@ -1388,7 +1371,8 @@ class MinerIdentity(betterproto.Message):
brand: "MinerBrand" = betterproto.enum_field(1)
model: "MinerModel" = betterproto.enum_field(2)
"""
Deprecated: Use miner_model instead. This field is no longer supported.
Deprecated: Use miner_model instead.
This field is no longer supported.
"""
name: str = betterproto.string_field(3)
@@ -1564,7 +1548,7 @@ class Hashboard(betterproto.Message):
stats: "WorkSolverStats" = betterproto.message_field(8)
"""Hashboard stats"""
model: Optional[str] = betterproto.string_field(9, optional=True, group="_model")
model: Optional[str] = betterproto.string_field(9, optional=True)
"""Hashboard model"""
@@ -1644,9 +1628,7 @@ class GetNetworkConfigurationResponse(betterproto.Message):
class SetNetworkConfigurationRequest(betterproto.Message):
dhcp: "Dhcp" = betterproto.message_field(1, group="protocol")
static: "Static" = betterproto.message_field(2, group="protocol")
hostname: Optional[str] = betterproto.string_field(
3, optional=True, group="_hostname"
)
hostname: Optional[str] = betterproto.string_field(3, optional=True)
"""Hostname. Existing value will be preserved if this field is not set."""
@@ -1691,27 +1673,21 @@ class GetNetworkInfoRequest(betterproto.Message):
@dataclass(eq=False, repr=False)
class GetNetworkInfoResponse(betterproto.Message):
"""
Response message for GetCurrentNetworkConfiguration Represents the current
network configuration for the default network interface. Only IPv4 is
supported.
Response message for GetCurrentNetworkConfiguration
Represents the current network configuration for the default network interface.
Only IPv4 is supported.
"""
name: str = betterproto.string_field(1)
"""Name of the network interface"""
mac_address: Optional[str] = betterproto.string_field(
2, optional=True, group="_mac_address"
)
mac_address: Optional[str] = betterproto.string_field(2, optional=True)
"""MAC address of the network interface"""
hostname: Optional[str] = betterproto.string_field(
3, optional=True, group="_hostname"
)
hostname: Optional[str] = betterproto.string_field(3, optional=True)
"""Miner hostname"""
protocol: Optional["NetworkProtocol"] = betterproto.enum_field(
4, optional=True, group="_protocol"
)
protocol: Optional["NetworkProtocol"] = betterproto.enum_field(4, optional=True)
"""Network protocol"""
dns_servers: List[str] = betterproto.string_field(5)
@@ -1720,9 +1696,7 @@ class GetNetworkInfoResponse(betterproto.Message):
networks: List["IpNetwork"] = betterproto.message_field(6)
"""List of assigned IP addresses"""
default_gateway: Optional[str] = betterproto.string_field(
7, optional=True, group="_default_gateway"
)
default_gateway: Optional[str] = betterproto.string_field(7, optional=True)
"""Default gateway/route for the interface"""
@@ -2332,7 +2306,7 @@ class MinerServiceStub(betterproto.ServiceStub):
timeout: Optional[float] = None,
deadline: Optional["Deadline"] = None,
metadata: Optional["MetadataLike"] = None
) -> AsyncIterator["GetMinerStatusResponse"]:
) -> AsyncIterator[GetMinerStatusResponse]:
async for response in self._unary_stream(
"/braiins.bos.v1.MinerService/GetMinerStatus",
get_miner_status_request,
@@ -2418,7 +2392,7 @@ class MinerServiceStub(betterproto.ServiceStub):
timeout: Optional[float] = None,
deadline: Optional["Deadline"] = None,
metadata: Optional["MetadataLike"] = None
) -> AsyncIterator["GetSupportArchiveResponse"]:
) -> AsyncIterator[GetSupportArchiveResponse]:
async for response in self._unary_stream(
"/braiins.bos.v1.MinerService/GetSupportArchive",
get_support_archive_request,
@@ -3195,7 +3169,7 @@ class MinerServiceBase(ServiceBase):
async def get_miner_status(
self, get_miner_status_request: "GetMinerStatusRequest"
) -> AsyncIterator["GetMinerStatusResponse"]:
) -> AsyncIterator[GetMinerStatusResponse]:
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
yield GetMinerStatusResponse()
@@ -3221,7 +3195,7 @@ class MinerServiceBase(ServiceBase):
async def get_support_archive(
self, get_support_archive_request: "GetSupportArchiveRequest"
) -> AsyncIterator["GetSupportArchiveResponse"]:
) -> AsyncIterator[GetSupportArchiveResponse]:
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
yield GetSupportArchiveResponse()

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyasic"
version = "0.60.4"
version = "0.61.2"
description = "A simplified and standardized interface for Bitcoin ASICs."
authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic"
@@ -8,21 +8,21 @@ documentation = "https://pyasic.readthedocs.io/en/latest/"
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.8"
python = "^3.9"
httpx = ">=0.26.0"
asyncssh = ">=2.14.2"
asyncssh = ">=2.17.0"
passlib = ">=1.7.4"
pyaml = ">=23.12.0"
tomli = { version = ">=2.0.1", python = "<3.11" }
tomli-w = "1.0.0"
betterproto = ">=2.0.0b6"
tomli-w = "^1.0.0"
betterproto = "2.0.0b7"
aiofiles = ">=23.2.1"
[tool.poetry.group.dev]
optional = true
[tool.poetry.group.dev.dependencies]
pre-commit = "^3.5.0"
pre-commit = "^4.0.1"
isort = "^5.12.0"
[tool.poetry.group.docs]