Compare commits

...

46 Commits

Author SHA1 Message Date
UpstreamData
0995744d90 version: bump version number. 2024-02-14 13:48:07 -07:00
UpstreamData
4073a27aba bug: update miner handling with unknown models, but known makes. 2024-02-14 13:47:34 -07:00
UpstreamData
bec9c31c97 docs: Update supported miners location. 2024-02-12 10:38:22 -07:00
UpstreamData
acdd615c53 docs: update docs location. 2024-02-12 10:37:33 -07:00
UpstreamData
8091617ee2 feature: add supports_power_modes flag. 2024-02-12 09:26:09 -07:00
Brett Rowan
ce288e472f version: bump version number. 2024-02-10 17:51:12 -07:00
Brett Rowan
02d8f25daf Merge pull request #108 from jpcomps/master
add ePIC UMC S21 support, fix HB generation by using capabilities
2024-02-10 17:50:37 -07:00
John-Paul Compagnone
a76d1c6149 add ePIC UMC S21 support, fix HB generation by using capabilities 2024-02-10 19:36:07 -05:00
Brett Rowan
17f5eade19 version: bump version number. 2024-02-10 16:45:49 -07:00
Brett Rowan
b6a2a5054b bug: fix goldshell sleep mode again. 2024-02-10 16:45:29 -07:00
Brett Rowan
5984338c64 version: bump version number. 2024-02-10 15:47:25 -07:00
Brett Rowan
07d1c48e33 bug: fix goldshell config/power modes. 2024-02-10 15:46:55 -07:00
Brett Rowan
d2abae947c version: bump version number. 2024-02-10 15:39:34 -07:00
Brett Rowan
e4a0f2451a Attempt to fix goldshell mode issues. 2024-02-10 15:39:00 -07:00
b-rowan
880c598b1a version: bump version number. 2024-02-10 14:26:09 -07:00
b-rowan
3632c2c4d8 feature: add support for goldshell shutdown. 2024-02-10 14:25:42 -07:00
b-rowan
09bc9686ae feature: add support for goldshell mode settings. 2024-02-10 14:23:17 -07:00
b-rowan
34584ab098 feature: add support for KDBoxPro and KDBoxII. 2024-02-10 13:56:00 -07:00
UpstreamData
554d99ca08 bug: update API unlocker format. 2024-02-09 14:25:14 -07:00
UpstreamData
5c5d688ffa Update api opener. 2024-02-09 13:39:48 -07:00
b-rowan
c50d55e87c version: bump version number. 2024-02-07 20:16:26 -07:00
b-rowan
5e5516bfb3 bug: fix serial numbers for antminer. 2024-02-07 20:15:38 -07:00
UpstreamData
4b068c57c5 version: bump version number. 2024-02-07 11:17:29 -07:00
UpstreamData
203f199aec feature: add wmt.pyasic.org. 2024-02-07 11:09:27 -07:00
b-rowan
895f17aaf9 version: bump version number. 2024-02-03 00:36:44 -07:00
b-rowan
8a64ff3559 bug: swap to asyncio.read() in base RPC to try to handle possible missed messages. 2024-02-03 00:36:03 -07:00
UpstreamData
4c45d356c4 version: bump version number. 2024-02-02 10:07:08 -07:00
UpstreamData
4dec329f11 bug: Try to return something when checking vnish fw version. 2024-02-02 10:06:33 -07:00
UpstreamData
b563ed118e bug: fix vnish firmware version bug. 2024-02-02 10:05:34 -07:00
UpstreamData
75b2ec40b1 bug: fix ePIC config parsing to use hashrate tuning instead of power tuning. 2024-01-31 09:21:32 -07:00
b-rowan
d9adaf6667 version: bump version number. 2024-01-30 21:41:49 -07:00
b-rowan
9343308f41 feature: Add support for new whatsminers, and try to handle whatsminer errors when receiving data. 2024-01-30 21:41:10 -07:00
UpstreamData
88769e40ae version: bump version number. 2024-01-30 13:34:24 -07:00
UpstreamData
be45eb7400 bug: fix issues with bosminer multicommand, and update X17 to use BOSMiner instead of BOSer. 2024-01-30 13:34:00 -07:00
b-rowan
2f719a03a4 version: bump version number. 2024-01-29 20:57:01 -07:00
b-rowan
64196f9754 bug: update whatsminer set_target_freq to match docs. 2024-01-29 20:56:36 -07:00
UpstreamData
49a77f1b79 version: bump version number. 2024-01-29 12:47:54 -07:00
UpstreamData
3838c4f2f9 bug: fix missing validation import for BTMiner. 2024-01-29 12:47:30 -07:00
UpstreamData
80d89c95b5 version: bump version number. 2024-01-29 12:33:07 -07:00
UpstreamData
30cd8b5cfe bug: fix some issues with rpc renaming. 2024-01-29 12:32:54 -07:00
b-rowan
c443170f78 refactor: improve epic web send_command implementation. 2024-01-27 09:42:35 -07:00
b-rowan
a2c2aa2377 version: bump version number. 2024-01-27 09:26:13 -07:00
b-rowan
4f0eb49a02 bug: fix some issues with epic send config. 2024-01-27 09:25:20 -07:00
b-rowan
a821357b4f Merge pull request #102 from jpcomps/master
fix send_config for ePIC
2024-01-27 09:05:23 -07:00
John-Paul Compagnone
3c7679a22d fix send_config for ePIC 2024-01-27 10:50:37 -05:00
UpstreamData
a52737e236 refactor: add some type hints for epic config. 2024-01-26 13:58:08 -07:00
46 changed files with 511 additions and 187 deletions

View File

@@ -9,7 +9,7 @@
[![Commit Activity - master](https://img.shields.io/github/commit-activity/y/UpstreamData/pyasic)](https://github.com/UpstreamData/pyasic/commits/master/)
[![Code Style - Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Read The Docs - Docs](https://img.shields.io/readthedocs/pyasic)](https://pyasic.readthedocs.io/en/latest/)
[![Read The Docs - Docs](https://img.shields.io/readthedocs/pyasic)](https://docs.pyasic.org)
[![License - Apache 2.0](https://img.shields.io/github/license/UpstreamData/pyasic)](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
---
@@ -17,7 +17,7 @@
Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with ASIC miners on your network, which makes it super fast.
[Click here to view supported miner types](miners/supported_types.md)
[Click here to view supported miner types](https://docs.pyasic.org/en/latest/miners/supported_types/)
---
## Getting started

View File

@@ -40,6 +40,7 @@ nav:
- Antminer X15: "miners/antminer/X15.md"
- Antminer X17: "miners/antminer/X17.md"
- Antminer X19: "miners/antminer/X19.md"
- Antminer X21: "miners/antminer/X21.md"
- Avalon 7X: "miners/avalonminer/A7X.md"
- Avalon 8X: "miners/avalonminer/A8X.md"
- Avalon 9X: "miners/avalonminer/A9X.md"

View File

@@ -54,9 +54,9 @@ class FanModeNormal(MinerConfigValue):
return {
"fans": {
"Auto": {
"Idle Speed": self.minimum_speed
if not self.minimum_speed == 0
else 100
"Idle Speed": (
self.minimum_speed if not self.minimum_speed == 0 else 100
)
}
}
}

View File

@@ -50,6 +50,9 @@ class MiningModeNormal(MinerConfigValue):
def as_epic(self) -> dict:
return {"ptune": {"enabled": False}}
def as_goldshell(self) -> dict:
return {"settings": {"level": 0}}
@dataclass
class MiningModeSleep(MinerConfigValue):
@@ -71,6 +74,9 @@ class MiningModeSleep(MinerConfigValue):
def as_epic(self) -> dict:
return {"ptune": {"algo": "Sleep", "target": 0}}
def as_goldshell(self) -> dict:
return {"settings": {"level": 3}}
@dataclass
class MiningModeLPM(MinerConfigValue):
@@ -89,6 +95,9 @@ class MiningModeLPM(MinerConfigValue):
def as_auradine(self) -> dict:
return {"mode": {"mode": "eco"}}
def as_goldshell(self) -> dict:
return {"settings": {"level": 1}}
@dataclass
class MiningModeHPM(MinerConfigValue):
@@ -108,31 +117,31 @@ class MiningModeHPM(MinerConfigValue):
return {"mode": {"mode": "turbo"}}
class StandardPowerTuneAlgo(MinerConfigValue):
class StandardTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
def as_epic(self):
return VOptPowerTuneAlgo().as_epic()
def as_epic(self) -> str:
return VOptAlgo().as_epic()
class VOptPowerTuneAlgo(MinerConfigValue):
class VOptAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
def as_epic(self):
def as_epic(self) -> str:
return "VoltageOptimizer"
class ChipTunePowerTuneAlgo(MinerConfigValue):
class ChipTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
def as_epic(self):
def as_epic(self) -> str:
return "ChipTune"
class PowerTunerAlgo(MinerConfigOption):
standard = StandardPowerTuneAlgo
voltage_optimizer = VOptPowerTuneAlgo
chip_tune = ChipTunePowerTuneAlgo
class TunerAlgo(MinerConfigOption):
standard = StandardTuneAlgo
voltage_optimizer = VOptAlgo
chip_tune = ChipTuneAlgo
@classmethod
def default(cls):
@@ -143,7 +152,7 @@ class PowerTunerAlgo(MinerConfigOption):
class MiningModePowerTune(MinerConfigValue):
mode: str = field(init=False, default="power_tuning")
power: int = None
algo: PowerTunerAlgo = field(default_factory=PowerTunerAlgo.default)
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune":
@@ -183,14 +192,12 @@ class MiningModePowerTune(MinerConfigValue):
def as_auradine(self) -> dict:
return {"mode": {"mode": "custom", "tune": "power", "power": self.power}}
def as_epic(self) -> dict:
return {"ptune": {**self.algo.as_epic(), "target": self.power}}
@dataclass
class MiningModeHashrateTune(MinerConfigValue):
mode: str = field(init=False, default="hashrate_tuning")
hashrate: int = None
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune":
@@ -218,6 +225,9 @@ class MiningModeHashrateTune(MinerConfigValue):
def as_auradine(self) -> dict:
return {"mode": {"mode": "custom", "tune": "ths", "ths": self.hashrate}}
def as_epic(self) -> dict:
return {"ptune": {"algo": self.algo.as_epic(), "target": self.hashrate}}
@dataclass
class ManualBoardSettings(MinerConfigValue):
@@ -313,14 +323,14 @@ class MiningModeConfig(MinerConfigOption):
if tuner_running:
algo_info = web_conf["PerpetualTune"]["Algorithm"]
if algo_info.get("VoltageOptimizer") is not None:
return cls.power_tuning(
power=algo_info["VoltageOptimizer"]["Target"],
algo=PowerTunerAlgo.voltage_optimizer,
return cls.hashrate_tuning(
hashrate=algo_info["VoltageOptimizer"]["Target"],
algo=TunerAlgo.voltage_optimizer,
)
else:
return cls.power_tuning(
power=algo_info["ChipTune"]["Target"],
algo=PowerTunerAlgo.chip_tune,
return cls.hashrate_tuning(
hashrate=algo_info["ChipTune"]["Target"],
algo=TunerAlgo.chip_tune,
)
else:
return cls.normal()

View File

@@ -265,15 +265,7 @@ class PoolGroup(MinerConfigValue):
return [p.as_auradine(user_suffix=user_suffix) for p in self.pools]
def as_epic(self, user_suffix: str = None) -> dict:
if len(self.pools) > 0:
conf = {
"name": self.name,
"pool": [pool.as_epic(user_suffix=user_suffix) for pool in self.pools],
}
if self.quota is not None:
conf["quota"] = self.quota
return conf
return {"name": self.name, "pool": []}
return [p.as_epic(user_suffix=user_suffix) for p in self.pools]
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "PoolGroup":
@@ -335,9 +327,11 @@ class PoolGroup(MinerConfigValue):
return cls(
pools=[Pool.from_boser(p) for p in grpc_pool_group["pools"]],
name=grpc_pool_group["name"],
quota=grpc_pool_group["quota"]["value"]
if grpc_pool_group.get("quota") is not None
else 1,
quota=(
grpc_pool_group["quota"]["value"]
if grpc_pool_group.get("quota") is not None
else 1
),
)
except LookupError:
return cls()
@@ -421,9 +415,7 @@ class PoolConfig(MinerConfigValue):
return {
"pools": {
"coin": "Btc",
"stratum_configs": [
g.as_epic(user_suffix=user_suffix) for g in self.groups
],
"stratum_configs": self.groups[0].as_epic(user_suffix=user_suffix),
"unique_id": False,
}
}

View File

@@ -43,9 +43,9 @@ class TemperatureConfig(MinerConfigValue):
def as_epic(self) -> dict:
temps_config = {"temps": {}, "fans": {"Auto": {}}}
if self.target is not None:
temps_config["fans"]["Target Temperature"] = self.target
temps_config["fans"]["Auto"]["Target Temperature"] = self.target
else:
temps_config["fans"]["Target Temperature"] = 60
temps_config["fans"]["Auto"]["Target Temperature"] = 60
if self.danger is not None:
temps_config["temps"]["shutdown"] = self.danger
return temps_config

View File

@@ -14,21 +14,21 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSer
from pyasic.miners.backends import BOSMiner
from pyasic.miners.models import S17, S17e, S17Plus, S17Pro
class BOSMinerS17(BOSer, S17):
class BOSMinerS17(BOSMiner, S17):
pass
class BOSMinerS17Plus(BOSer, S17Plus):
class BOSMinerS17Plus(BOSMiner, S17Plus):
pass
class BOSMinerS17Pro(BOSer, S17Pro):
class BOSMinerS17Pro(BOSMiner, S17Pro):
pass
class BOSMinerS17e(BOSer, S17e):
class BOSMinerS17e(BOSMiner, S17e):
pass

View File

@@ -14,17 +14,17 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSer
from pyasic.miners.backends import BOSMiner
from pyasic.miners.models import T17, T17e, T17Plus
class BOSMinerT17(BOSer, T17):
class BOSMinerT17(BOSMiner, T17):
pass
class BOSMinerT17Plus(BOSer, T17Plus):
class BOSMinerT17Plus(BOSMiner, T17Plus):
pass
class BOSMinerT17e(BOSer, T17e):
class BOSMinerT17e(BOSMiner, T17e):
pass

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 ePIC
from pyasic.miners.models import S21
class ePICS21(ePIC, S21):
pass

View File

@@ -0,0 +1,19 @@
# ------------------------------------------------------------------------------
# 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 (
ePICS21,
)

View File

@@ -15,3 +15,4 @@
# ------------------------------------------------------------------------------
from .X19 import *
from .X21 import *

View File

@@ -94,6 +94,7 @@ class AntminerModern(BMMiner):
data_locations = ANTMINER_MODERN_DATA_LOC
supports_shutdown = True
supports_power_modes = True
async def get_config(self) -> MinerConfig:
data = await self.web.get_miner_conf()
@@ -206,7 +207,7 @@ class AntminerModern(BMMiner):
]
try:
rpc_stats = await self.rpc.send_command("stats", new_rpc=True)
rpc_stats = await self.rpc.send_command("stats", new_api=True)
except APIError:
return hashboards

View File

@@ -124,6 +124,7 @@ class Auradine(BaseMiner):
data_locations = AURADINE_DATA_LOC
supports_shutdown = True
supports_power_modes = True
supports_autotuning = True
async def fault_light_on(self) -> bool:

View File

@@ -68,7 +68,7 @@ class BFGMiner(BaseMiner):
except APIError:
return self.config
self.config = MinerConfig.from_rpc(pools)
self.config = MinerConfig.from_api(pools)
return self.config
##################################################
@@ -84,11 +84,11 @@ class BFGMiner(BaseMiner):
if rpc_version is not None:
try:
self.rpc_ver = rpc_version["VERSION"][0]["API"]
self.api_ver = rpc_version["VERSION"][0]["API"]
except LookupError:
pass
return self.rpc_ver
return self.api_ver
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
if rpc_version is None:

View File

@@ -72,7 +72,7 @@ class BMMiner(BaseMiner):
except APIError:
return self.config
self.config = MinerConfig.from_rpc(pools)
self.config = MinerConfig.from_api(pools)
return self.config
##################################################
@@ -88,11 +88,11 @@ class BMMiner(BaseMiner):
if rpc_version is not None:
try:
self.rpc_ver = rpc_version["VERSION"][0]["API"]
self.api_ver = rpc_version["VERSION"][0]["API"]
except LookupError:
pass
return self.rpc_ver
return self.api_ver
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
if rpc_version is None:

View File

@@ -305,10 +305,10 @@ class BOSMiner(BaseMiner):
rpc_ver = rpc_version["VERSION"][0]["API"]
except LookupError:
rpc_ver = None
self.rpc_ver = rpc_ver
self.rpc.rpc_ver = self.rpc_ver
self.api_ver = rpc_ver
self.rpc.rpc_ver = self.api_ver
return self.rpc_ver
return self.api_ver
async def _get_fw_ver(self, web_bos_info: dict = None) -> Optional[str]:
if web_bos_info is None:
@@ -731,10 +731,10 @@ class BOSer(BaseMiner):
rpc_ver = rpc_version["VERSION"][0]["API"]
except LookupError:
rpc_ver = None
self.rpc_ver = rpc_ver
self.rpc.rpc_ver = self.rpc_ver
self.api_ver = rpc_ver
self.rpc.rpc_ver = self.api_ver
return self.rpc_ver
return self.api_ver
async def _get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]:
if grpc_miner_details is None:

View File

@@ -119,6 +119,7 @@ class BTMiner(BaseMiner):
data_locations = BTMINER_DATA_LOC
supports_shutdown = True
supports_power_modes = True
async def _reset_rpc_pwd_to_admin(self, pwd: str):
try:
@@ -234,7 +235,7 @@ class BTMiner(BaseMiner):
pass
if pools is not None:
cfg = MinerConfig.from_rpc(pools)
cfg = MinerConfig.from_api(pools)
else:
cfg = MinerConfig()
@@ -325,14 +326,14 @@ class BTMiner(BaseMiner):
rpc_ver = rpc_get_version["Msg"]
if not isinstance(rpc_ver, str):
rpc_ver = rpc_ver["rpc_ver"]
self.rpc_ver = rpc_ver.replace("whatsminer v", "")
self.api_ver = rpc_ver.replace("whatsminer v", "")
except (KeyError, TypeError):
pass
else:
self.rpc.rpc_ver = self.rpc_ver
return self.rpc_ver
self.rpc.rpc_ver = self.api_ver
return self.api_ver
return self.rpc_ver
return self.api_ver
async def _get_fw_ver(
self, rpc_get_version: dict = None, rpc_summary: dict = None

View File

@@ -71,7 +71,7 @@ class CGMiner(BaseMiner):
except APIError:
return self.config
self.config = MinerConfig.from_rpc(pools)
self.config = MinerConfig.from_api(pools)
return self.config
##################################################
@@ -87,11 +87,11 @@ class CGMiner(BaseMiner):
if rpc_version is not None:
try:
self.rpc_ver = rpc_version["VERSION"][0]["API"]
self.api_ver = rpc_version["VERSION"][0]["API"]
except LookupError:
pass
return self.rpc_ver
return self.api_ver
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
if rpc_version is None:

View File

@@ -51,7 +51,7 @@ EPIC_DATA_LOC = DataLocations(
"_get_hashboards",
[
WebAPICommand("web_summary", "summary"),
WebAPICommand("web_hashrate", "hashrate"),
WebAPICommand("web_capabilities", "capabilities"),
],
),
str(DataOptions.WATTAGE): DataFunction(
@@ -116,6 +116,7 @@ class ePIC(BaseMiner):
if not conf.get("temps", {}) == {}:
await self.web.set_shutdown_temp(conf["temps"]["shutdown"])
# Fans
# set with sub-keys instead of conf["fans"] because sometimes both can be set
if not conf["fans"].get("Manual", {}) == {}:
await self.web.set_fan({"Manual": conf["fans"]["Manual"]})
elif not conf["fans"].get("Auto", {}) == {}:
@@ -283,7 +284,7 @@ class ePIC(BaseMiner):
return fans
async def _get_hashboards(
self, web_summary: dict = None, web_hashrate: dict = None
self, web_summary: dict = None, web_capabilities: dict = None
) -> List[HashBoard]:
if web_summary is None:
try:
@@ -291,28 +292,25 @@ class ePIC(BaseMiner):
except APIError:
pass
if web_hashrate is not None:
if web_capabilities is not None:
try:
web_hashrate = await self.web.hashrate()
web_capabilities = await self.web.capabilities()
except APIError:
pass
hb_list = [
HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards)
]
if web_summary.get("HBs") is not None:
for hb in web_summary["HBs"]:
for hr in web_hashrate:
if hr["Index"] == hb["Index"]:
num_of_chips = len(hr["Data"])
hashrate = hb["Hashrate"][0]
# Update the Hashboard object
hb_list[hr["Index"]].expected_chips = num_of_chips
hb_list[hr["Index"]].missing = False
hb_list[hr["Index"]].hashrate = round(hashrate / 1000000, 2)
hb_list[hr["Index"]].chips = num_of_chips
hb_list[hr["Index"]].temp = hb["Temperature"]
num_of_chips = web_capabilities["Performance Estimator"]["Chip Count"]
hashrate = hb["Hashrate"][0]
# Update the Hashboard object
hb_list[hb["Index"]].missing = False
hb_list[hb["Index"]].hashrate = round(hashrate / 1000000, 2)
hb_list[hb["Index"]].chips = num_of_chips
hb_list[hb["Index"]].temp = hb["Temperature"]
return hb_list
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------
from typing import List
from pyasic.config import MinerConfig
from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import HashBoard
from pyasic.errors import APIError
from pyasic.logger import logger
@@ -74,6 +74,9 @@ class GoldshellMiner(BFGMiner):
data_locations = GOLDSHELL_DATA_LOC
supports_shutdown = True
supports_power_modes = True
async def get_config(self) -> MinerConfig:
# get pool data
try:
@@ -96,13 +99,19 @@ class GoldshellMiner(BFGMiner):
)
self.config = config
cfg = config.as_goldshell(user_suffix=user_suffix)
# send them back 1 at a time
for pool in config.as_goldshell(user_suffix=user_suffix)["pools"]:
for pool in cfg["pools"]:
await self.web.newpool(
url=pool["url"], user=pool["user"], password=pool["pass"]
)
settings = await self.web.setting()
for idx, plan in enumerate(settings["powerplans"]):
if plan["level"] == cfg["settings"]["level"]:
settings["select"] = idx
await self.web.set_setting(settings)
async def _get_mac(self, web_setting: dict = None) -> str:
if web_setting is None:
try:
@@ -178,3 +187,25 @@ class GoldshellMiner(BFGMiner):
logger.error(self, rpc_devdetails)
return hashboards
async def stop_mining(self) -> bool:
settings = await self.web.setting()
mode = MiningModeConfig.sleep()
cfg = mode.as_goldshell()
level = cfg["settings"]["level"]
for idx, plan in enumerate(settings["powerplans"]):
if plan["level"] == level:
settings["select"] = idx
await self.web.set_setting(settings)
return True
async def resume_mining(self) -> bool:
settings = await self.web.setting()
mode = MiningModeConfig.normal()
cfg = mode.as_goldshell()
level = cfg["settings"]["level"]
for idx, plan in enumerate(settings["powerplans"]):
if plan["level"] == level:
settings["select"] = idx
await self.web.set_setting(settings)
return True

View File

@@ -205,13 +205,14 @@ class VNish(BMMiner):
if web_summary is None:
web_summary = await self.web.summary()
fw_ver = None
if web_summary is not None:
try:
fw_ver = web_summary["miner"]["miner_type"]
fw_ver = fw_ver.split("(Vnish ")[1].replace(")", "")
return fw_ver
except KeyError:
pass
except LookupError:
return fw_ver
async def get_config(self) -> MinerConfig:
try:

View File

@@ -47,6 +47,7 @@ class MinerProtocol(Protocol):
data_locations: DataLocations = None
supports_shutdown: bool = False
supports_power_modes: bool = False
supports_autotuning: bool = False
api_ver: str = None
@@ -68,7 +69,12 @@ class MinerProtocol(Protocol):
@property
def model(self) -> str:
model_data = [self.raw_model if self.raw_model is not None else "Unknown"]
if self.raw_model is not None:
model_data = [self.raw_model]
elif self.make is not None:
model_data = [self.make]
else:
model_data = ["Unknown"]
if self.firmware is not None:
model_data.append(f"({self.firmware})")
return " ".join(model_data)
@@ -461,9 +467,11 @@ class MinerProtocol(Protocol):
ip=str(self.ip),
make=self.make,
model=self.model,
expected_chips=self.expected_chips * self.expected_hashboards
if self.expected_chips is not None
else 0,
expected_chips=(
self.expected_chips * self.expected_hashboards
if self.expected_chips is not None
else 0
),
expected_hashboards=self.expected_hashboards,
hashboards=[
HashBoard(slot=i, expected_chips=self.expected_chips)

View File

@@ -47,6 +47,7 @@ from pyasic.miners.backends.unknown import UnknownMiner
from pyasic.miners.base import AnyMiner
from pyasic.miners.goldshell import *
from pyasic.miners.innosilicon import *
from pyasic.miners.makes import *
from pyasic.miners.whatsminer import *
@@ -66,7 +67,7 @@ class MinerTypes(enum.Enum):
MINER_CLASSES = {
MinerTypes.ANTMINER: {
None: BMMiner,
None: type("AntminerUnknown", (BMMiner, AntMinerMake), {}),
"ANTMINER D3": CGMinerD3,
"ANTMINER HS3": BMMinerHS3,
"ANTMINER L3+": BMMinerL3Plus,
@@ -101,7 +102,7 @@ MINER_CLASSES = {
"ANTMINER T19": BMMinerT19,
},
MinerTypes.WHATSMINER: {
None: BTMiner,
None: type("WhatsminerUnknown", (BTMiner, WhatsMinerMake), {}),
"M20V10": BTMinerM20V10,
"M20SV10": BTMinerM20SV10,
"M20SV20": BTMinerM20SV20,
@@ -289,7 +290,9 @@ MINER_CLASSES = {
"M50S++VK30": BTMinerM50SPlusPlusVK30,
"M53VH30": BTMinerM53VH30,
"M53SVH30": BTMinerM53SVH30,
"M53SVJ40": BTMinerM53SVJ40,
"M53S+VJ30": BTMinerM53SPlusVJ30,
"M53S++VK10": BTMinerM53SPlusPlusVK10,
"M56VH30": BTMinerM56VH30,
"M56SVH30": BTMinerM56SVH30,
"M56S+VJ30": BTMinerM56SPlusVJ30,
@@ -315,7 +318,7 @@ MINER_CLASSES = {
"M66SVK40": BTMinerM66SVK40,
},
MinerTypes.AVALONMINER: {
None: AvalonMiner,
None: type("AvalonUnknown", (AvalonMiner, AvalonMinerMake), {}),
"AVALONMINER 721": CGMinerAvalon721,
"AVALONMINER 741": CGMinerAvalon741,
"AVALONMINER 761": CGMinerAvalon761,
@@ -330,16 +333,18 @@ MINER_CLASSES = {
"AVALONMINER 1246": CGMinerAvalon1246,
},
MinerTypes.INNOSILICON: {
None: Innosilicon,
None: type("InnosiliconUnknown", (Innosilicon, InnosiliconMake), {}),
"T3H+": InnosiliconT3HPlus,
"A10X": InnosiliconA10X,
},
MinerTypes.GOLDSHELL: {
None: GoldshellMiner,
None: type("GoldshellUnknown", (GoldshellMiner, GoldshellMake), {}),
"GOLDSHELL CK5": GoldshellCK5,
"GOLDSHELL HS5": GoldshellHS5,
"GOLDSHELL KD5": GoldshellKD5,
"GOLDSHELL KDMAX": GoldshellKDMax,
"GOLDSHELL KDBOXII": GoldshellKDBoxII,
"GOLDSHELL KDBOXPRO": GoldshellKDBoxPro,
},
MinerTypes.BRAIINS_OS: {
None: BOSMiner,
@@ -388,6 +393,7 @@ MINER_CLASSES = {
"ANTMINER S19J PRO+": ePICS19jProPlus,
"ANTMINER S19K PRO": ePICS19kPro,
"ANTMINER S19 XP": ePICS19XP,
"ANTMINER S21": ePICS21,
},
MinerTypes.HIVEON: {
None: Hiveon,
@@ -398,7 +404,7 @@ MINER_CLASSES = {
"ANTMINER S9": LUXMinerS9,
},
MinerTypes.AURADINE: {
None: Auradine,
None: type("GoldshellUnknown", (Auradine, AuradineMake), {}),
"AT1500": AuradineFluxAT1500,
"AT2860": AuradineFluxAT2860,
"AT2880": AuradineFluxAT2880,
@@ -494,7 +500,6 @@ class MinerFactory:
)
except asyncio.TimeoutError:
pass
miner = self._select_miner_from_classes(
ip,
miner_type=miner_type,
@@ -666,7 +671,11 @@ class MinerFactory:
return MinerTypes.LUX_OS
if "ANTMINER" in upper_data and "DEVDETAILS" not in upper_data:
return MinerTypes.ANTMINER
if "INTCHAINS_QOMO" in upper_data:
if (
"INTCHAINS_QOMO" in upper_data
or "KDAMINER" in upper_data
or "BFGMINER" in upper_data
):
return MinerTypes.GOLDSHELL
if "AVALON" in upper_data:
return MinerTypes.AVALONMINER
@@ -968,6 +977,7 @@ class MinerFactory:
miner_factory = MinerFactory()
# abstracted version of get miner that is easier to access
async def get_miner(ip: ipaddress.ip_address | str) -> AnyMiner:
return await miner_factory.get_miner(ip)

View File

@@ -0,0 +1,25 @@
# ------------------------------------------------------------------------------
# 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 GoldshellMiner
from pyasic.miners.models import KDBoxII, KDBoxPro
class GoldshellKDBoxII(GoldshellMiner, KDBoxII):
pass
class GoldshellKDBoxPro(GoldshellMiner, KDBoxPro):
pass

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 .KDBox import GoldshellKDBoxII, GoldshellKDBoxPro

View File

@@ -14,4 +14,5 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .X5 import *
from .XBox import *
from .XMax import *

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.miners.makes import AntMinerMake
class S21(AntMinerMake):
raw_model = "S21"
expected_chips = 108
expected_fans = 4

View File

@@ -0,0 +1,19 @@
# ------------------------------------------------------------------------------
# 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 (
S21,
)

View File

@@ -20,3 +20,4 @@ from .X9 import *
from .X15 import *
from .X17 import *
from .X19 import *
from .X21 import *

View File

@@ -0,0 +1,30 @@
# ------------------------------------------------------------------------------
# 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.makes import GoldshellMake
class KDBoxII(GoldshellMake):
raw_model = "KD Box II"
expected_chips = 36
expected_fans = 2
expected_hashboards = 1
class KDBoxPro(GoldshellMake):
raw_model = "KD Box Pro"
expected_chips = 16
expected_fans = 2
expected_hashboards = 1

View File

@@ -0,0 +1 @@
from .KDBox import KDBoxII, KDBoxPro

View File

@@ -14,4 +14,5 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .X5 import *
from .XBox import *
from .XMax import *

View File

@@ -20,3 +20,8 @@ from pyasic.miners.makes import WhatsMinerMake
class M53SVH30(WhatsMinerMake):
raw_model = "M53S VH30"
expected_fans = 0
class M53SVJ40(WhatsMinerMake):
raw_model = "M53S VJ40"
expected_fans = 0

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.makes import WhatsMinerMake
class M53SPlusPlusVK10(WhatsMinerMake):
raw_model = "M53S++ VK10"
expected_fans = 0

View File

@@ -42,8 +42,9 @@ from .M50S import (
from .M50S_Plus import M50SPlusVH30, M50SPlusVH40, M50SPlusVJ30, M50SPlusVK20
from .M50S_Plus_Plus import M50SPlusPlusVK10, M50SPlusPlusVK20, M50SPlusPlusVK30
from .M53 import M53VH30
from .M53S import M53SVH30
from .M53S import M53SVH30, M53SVJ40
from .M53S_Plus import M53SPlusVJ30
from .M53S_Plus_Plus import M53SPlusPlusVK10
from .M56 import M56VH30
from .M56S import M56SVH30
from .M56S_Plus import M56SPlusVJ30

View File

@@ -15,8 +15,12 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import M5X
from pyasic.miners.models import M53SVH30
from pyasic.miners.models import M53SVH30, M53SVJ40
class BTMinerM53SVH30(M5X, M53SVH30):
pass
class BTMinerM53SVJ40(M5X, M53SVJ40):
pass

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 M5X
from pyasic.miners.models import M53SPlusPlusVK10
class BTMinerM53SPlusPlusVK10(M5X, M53SPlusPlusVK10):
pass

View File

@@ -51,8 +51,9 @@ from .M50S_Plus_Plus import (
BTMinerM50SPlusPlusVK30,
)
from .M53 import BTMinerM53VH30
from .M53S import BTMinerM53SVH30
from .M53S import BTMinerM53SVH30, BTMinerM53SVJ40
from .M53S_Plus import BTMinerM53SPlusVJ30
from .M53S_Plus_Plus import BTMinerM53SPlusPlusVK10
from .M56 import BTMinerM56VH30
from .M56S import BTMinerM56SVH30
from .M56S_Plus import BTMinerM56SPlusVJ30

View File

@@ -107,3 +107,4 @@ def validate_command_output(data: dict) -> tuple[bool, str | None]:
if data[key][0]["STATUS"][0]["STATUS"] not in ["S", "I"]:
# this is an error
return False, f"{key}: " + data[key][0]["STATUS"][0]["Msg"]
return True, None

View File

@@ -78,6 +78,9 @@ class BaseMinerRPCAPI:
# send the command
data = await self._send_bytes(json.dumps(cmd).encode("utf-8"))
if data is None:
raise APIError("No data returned from the API.")
if data == b"Socket connect failed: Connection refused\n":
if not ignore_errors:
raise APIError(data.decode("utf-8"))
@@ -90,7 +93,7 @@ class BaseMinerRPCAPI:
if not validation[0]:
if not ignore_errors:
# validate the command succeeded
raise APIError(validation[1])
raise APIError(f"{command}: {validation[1]}")
if allow_warning:
logging.warning(
f"{self.ip}: API Command Error: {command}: {validation[1]}"
@@ -162,7 +165,7 @@ class BaseMinerRPCAPI:
for func in
# each function in self
dir(self)
if not func == "commands"
if not func in ["commands", "open_api"]
if callable(getattr(self, func)) and
# no __ or _ methods
not func.startswith("__") and not func.startswith("_") and
@@ -193,12 +196,15 @@ If you are sure you want to use this command please use API.send_command("{comma
async def _send_bytes(
self,
data: bytes,
port: int = None,
timeout: int = 100,
) -> bytes:
if port is None:
port = self.port
logging.debug(f"{self} - ([Hidden] Send Bytes) - Sending")
try:
# get reader and writer streams
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
reader, writer = await asyncio.open_connection(str(self.ip), port)
# handle OSError 121
except OSError as e:
if e.errno == 121:
@@ -208,39 +214,14 @@ If you are sure you want to use this command please use API.send_command("{comma
return b"{}"
# send the command
data_task = asyncio.create_task(self._read_bytes(reader, timeout=timeout))
logging.debug(f"{self} - ([Hidden] Send Bytes) - Writing")
writer.write(data)
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
await writer.drain()
try:
# TO address a situation where a whatsminer has an unknown PW -AND-
# Fix for stupid whatsminer bug, reboot/restart seem to not load properly in the loop
# have to receive, save the data, check if there is more data by reading with a short timeout
# append that data if there is more, and then onto the main loop.
# the password timeout might need to be longer than 1, but it seems to work for now.
ret_data = await asyncio.wait_for(reader.read(1), timeout=1)
except asyncio.TimeoutError:
return b"{}"
try:
ret_data += await asyncio.wait_for(reader.read(4096), timeout=timeout)
except ConnectionAbortedError:
return b"{}"
# loop to receive all the data
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
try:
while True:
try:
d = await asyncio.wait_for(reader.read(4096), timeout=timeout)
if not d:
break
ret_data += d
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
raise e
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
raise e
except Exception as e:
logging.warning(f"{self} - ([Hidden] Send Bytes) - API Command Error {e}")
await data_task
ret_data = data_task.result()
# close the connection
logging.debug(f"{self} - ([Hidden] Send Bytes) - Closing")
@@ -249,6 +230,19 @@ If you are sure you want to use this command please use API.send_command("{comma
return ret_data
async def _read_bytes(self, reader: asyncio.StreamReader, timeout: int) -> bytes:
ret_data = b""
# loop to receive all the data
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
try:
ret_data = await asyncio.wait_for(reader.read(), timeout=timeout)
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
raise e
except Exception as e:
logging.warning(f"{self} - ([Hidden] Send Bytes) - API Command Error {e}")
return ret_data
@staticmethod
def _load_api_data(data: bytes) -> dict:
# some json from the API returns with a null byte (\x00) on the end

View File

@@ -24,12 +24,13 @@ import logging
import re
from typing import Literal, Union
import httpx
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from passlib.handlers.md5_crypt import md5_crypt
from pyasic import settings
from pyasic.errors import APIError
from pyasic.misc import api_min_version
from pyasic.misc import api_min_version, validate_command_output
from pyasic.rpc.base import BaseMinerRPCAPI
### IMPORTANT ###
@@ -240,6 +241,28 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
ignore_errors: bool = False,
timeout: int = 10,
**kwargs,
) -> dict:
try:
return await self._send_privileged_command(
command=command, ignore_errors=ignore_errors, timeout=timeout, **kwargs
)
except APIError as e:
if not e.message == "can't access write cmd":
raise
try:
await self.open_api()
except Exception as e:
raise APIError("Failed to open whatsminer API.") from e
return await self._send_privileged_command(
command=command, ignore_errors=ignore_errors, timeout=timeout, **kwargs
)
async def _send_privileged_command(
self,
command: Union[str, bytes],
ignore_errors: bool = False,
timeout: int = 10,
**kwargs,
) -> dict:
logging.debug(
f"{self} - (Send Privileged Command) - {command} " + f"with args {kwargs}"
@@ -272,7 +295,7 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
if not ignore_errors:
# if it fails to validate, it is likely an error
validation = self._validate_command_output(data)
validation = validate_command_output(data)
if not validation[0]:
raise APIError(validation[1])
@@ -321,6 +344,36 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
logging.debug(f"{self} - (Get Token) - Gathered token data: {self.token}")
return self.token
async def open_api(self):
async with httpx.AsyncClient() as c:
stage1_req = (
await c.post(
"https://wmt.pyasic.org/v1/stage1",
json={"ip": str(self.ip)},
follow_redirects=True,
)
).json()
stage1_res = binascii.hexlify(
await self._send_bytes(binascii.unhexlify(stage1_req), port=8889)
)
stage2_req = (
await c.post(
"https://wmt.pyasic.org/v1/stage2",
json={
"ip": str(self.ip),
"stage1_result": stage1_res.decode("utf-8"),
},
)
).json()
for command in stage2_req:
try:
await self._send_bytes(
binascii.unhexlify(command), timeout=3, port=8889
)
except asyncio.TimeoutError:
pass
return True
#### PRIVILEGED COMMANDS ####
# Please read the top of this file to learn
# how to configure the Whatsminer API to
@@ -607,10 +660,10 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
A reply informing of the status of setting the frequency.
</details>
"""
if not -10 < percent < 100:
if not -100 < percent < 100:
raise APIError(
f"Frequency % is outside of the allowed "
f"range. Please set a % between -10 and "
f"range. Please set a % between -100 and "
f"100"
)
return await self.send_privileged_command(

View File

@@ -70,13 +70,15 @@ class BOSerWebAPI(BaseWebAPI):
not func.startswith("__") and not func.startswith("_")
]
async def multicommand(self, *commands: str) -> dict:
async def multicommand(
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
) -> dict:
result = {"multicommand": True}
tasks = {}
for command in commands:
try:
tasks[command] = asyncio.create_task(getattr(self, command)())
except AttributeError:
except (APIError, AttributeError):
result["command"] = {}
await asyncio.gather(*list(tasks.values()))

View File

@@ -59,17 +59,23 @@ class BOSMinerWebAPI(BaseWebAPI):
return {}
raise APIError(f"LUCI web command failed: command={command}")
async def multicommand(self, *commands: str) -> dict:
async def multicommand(
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
) -> dict:
data = {}
for command in commands:
data[command] = await self.send_command(command, ignore_errors=True)
data[command] = await self.send_command(
command, ignore_errors=ignore_errors
)
return data
async def auth(self, session: httpx.AsyncClient) -> None:
login = {"luci_username": self.username, "luci_password": self.pwd}
url = f"http://{self.ip}:{self.port}/cgi-bin/luci"
headers = {
"User-Agent": "BTC Tools v0.1", # only seems to respond if this user-agent is set
"User-Agent": (
"BTC Tools v0.1"
), # only seems to respond if this user-agent is set
"Content-Type": "application/x-www-form-urlencoded",
}
await session.post(url, headers=headers, data=login)

View File

@@ -44,40 +44,37 @@ class ePICWebAPI(BaseWebAPI):
post = privileged or not parameters == {}
async with httpx.AsyncClient(transport=settings.transport()) as client:
for i in range(settings.get("get_data_retries", 1) + 1):
try:
if post:
response = await client.post(
f"http://{self.ip}:{self.port}/{command}",
timeout=5,
json={
**parameters,
"password": self.pwd,
},
try:
if post:
response = await client.post(
f"http://{self.ip}:{self.port}/{command}",
timeout=5,
json={
**parameters,
"password": self.pwd,
},
)
else:
response = await client.get(
f"http://{self.ip}:{self.port}/{command}",
timeout=5,
)
if not response.status_code == 200:
if not ignore_errors:
raise APIError(
f"Web command {command} failed with status code {response.status_code}"
)
else:
response = await client.get(
f"http://{self.ip}:{self.port}/{command}",
timeout=5,
)
if not response.status_code == 200:
continue
json_data = response.json()
if json_data:
# The API can return a fail status if the miner cannot return the requested data. Catch this and pass
if (
"result" in json_data
and json_data["result"] is False
and not post
):
if not i > settings.get("get_data_retries", 1):
continue
if not ignore_errors:
raise APIError(json_data["error"])
return json_data
return {"success": True}
except (httpx.HTTPError, json.JSONDecodeError, AttributeError):
pass
return {}
json_data = response.json()
if json_data:
# The API can return a fail status if the miner cannot return the requested data. Catch this and pass
if not json_data.get("result", True) and not post:
if not ignore_errors:
raise APIError(json_data["error"])
return json_data
return {"success": True}
except (httpx.HTTPError, json.JSONDecodeError, AttributeError):
pass
async def multicommand(
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
@@ -95,19 +92,19 @@ class ePICWebAPI(BaseWebAPI):
return await self.send_command("reboot", privileged=True)
async def set_shutdown_temp(self, params: int) -> dict:
return await self.send_command("shutdowntemp", parameters=params)
return await self.send_command("shutdowntemp", param=params)
async def set_fan(self, params: dict) -> dict:
return await self.send_command("fanspeed", parameters=params)
return await self.send_command("fanspeed", param=params)
async def set_ptune_enable(self, params: bool) -> dict:
return await self.send_command("perpetualtune", parameters=params)
return await self.send_command("perpetualtune", param=params)
async def set_ptune_algo(self, params: dict) -> dict:
return await self.send_command("perpetualtune/algo", parameters=params)
return await self.send_command("perpetualtune/algo", param=params)
async def set_pools(self, params: dict) -> dict:
return await self.send_command("coin", parameters=params)
return await self.send_command("coin", param=params)
async def pause_mining(self) -> dict:
return await self.send_command("miner", param="Stop")

View File

@@ -138,5 +138,8 @@ class GoldshellWebAPI(BaseWebAPI):
async def setting(self) -> dict:
return await self.send_command("setting")
async def set_setting(self, values: dict):
await self.send_command("setting", **values)
async def status(self) -> dict:
return await self.send_command("status")

View File

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