Compare commits

...

10 Commits

Author SHA1 Message Date
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
14 changed files with 69 additions and 80 deletions

View File

@@ -111,21 +111,21 @@ class MiningModeHPM(MinerConfigValue):
class StandardPowerTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
def as_epic(self):
def as_epic(self) -> str:
return VOptPowerTuneAlgo().as_epic()
class VOptPowerTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
def as_epic(self):
def as_epic(self) -> str:
return "VoltageOptimizer"
class ChipTunePowerTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
def as_epic(self):
def as_epic(self) -> str:
return "ChipTune"
@@ -184,7 +184,7 @@ class MiningModePowerTune(MinerConfigValue):
return {"mode": {"mode": "custom", "tune": "power", "power": self.power}}
def as_epic(self) -> dict:
return {"ptune": {**self.algo.as_epic(), "target": self.power}}
return {"ptune": {"algo": self.algo.as_epic(), "target": self.power}}
@dataclass

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":
@@ -421,9 +413,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

@@ -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

@@ -234,7 +234,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 +325,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

@@ -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", {}) == {}:

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

@@ -90,7 +90,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]}"

View File

@@ -29,7 +29,7 @@ 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 ###
@@ -272,7 +272,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])

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

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