Compare commits

..

11 Commits

Author SHA1 Message Date
Upstream Data
f2a4a5d524 version: bump version number 2025-01-08 11:24:08 -07:00
Wilfred Allyn
624a3c5919 feature: revise atmset input params 2025-01-08 11:22:46 -07:00
Wilfred Allyn
2ec8054d24 feature: check if atm enabled before settting power 2025-01-08 11:22:46 -07:00
Wilfred Allyn
d148ccfe5f feature: add atm, atmset commands to luxos rpc 2025-01-08 11:22:46 -07:00
Wilfred Allyn
b6c29d16f9 feature: add set_power_limit for luxos 2025-01-08 11:22:46 -07:00
Wilfred Allyn
53a3bb13af feature: remove deprecated board_n param from profileset 2025-01-08 11:22:46 -07:00
John-Paul Compagnone
16e74e659c add expected_hashrate for bitaxe devices 2025-01-07 09:59:51 -07:00
Upstream Data
730caca23f version: bump version number 2025-01-07 08:57:09 -07:00
Upstream Data
dc126b2953 bug: fix a bug with config on S9 sometimes not having quota set. 2025-01-07 08:56:39 -07:00
Brett Rowan
51abdf0b2d version: bump version number 2025-01-04 11:50:08 -07:00
Brett Rowan
b367b2d293 bug: fix an issue with parsing disabled fan mode on BOS+ 2025-01-04 11:49:49 -07:00
7 changed files with 168 additions and 24 deletions

34
poetry.lock generated
View File

@@ -24,13 +24,13 @@ files = [
[[package]]
name = "anyio"
version = "4.7.0"
version = "4.8.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.9"
files = [
{file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"},
{file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"},
{file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"},
{file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"},
]
[package.dependencies]
@@ -41,7 +41,7 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
[package.extras]
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
trio = ["trio (>=0.26.1)"]
[[package]]
@@ -562,13 +562,13 @@ files = [
[[package]]
name = "identify"
version = "2.6.4"
version = "2.6.5"
description = "File identification library for Python"
optional = false
python-versions = ">=3.9"
files = [
{file = "identify-2.6.4-py2.py3-none-any.whl", hash = "sha256:993b0f01b97e0568c179bb9196391ff391bfb88a99099dbf5ce392b68f42d0af"},
{file = "identify-2.6.4.tar.gz", hash = "sha256:285a7d27e397652e8cafe537a6cc97dd470a970f48fb2e9d979aa38eae5513ac"},
{file = "identify-2.6.5-py2.py3-none-any.whl", hash = "sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566"},
{file = "identify-2.6.5.tar.gz", hash = "sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc"},
]
[package.extras]
@@ -1255,13 +1255,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pygments"
version = "2.18.0"
version = "2.19.1"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
files = [
{file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
{file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"},
{file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"},
]
[package.extras]
@@ -1269,13 +1269,13 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pymdown-extensions"
version = "10.13"
version = "10.14"
description = "Extension pack for Python Markdown."
optional = false
python-versions = ">=3.8"
files = [
{file = "pymdown_extensions-10.13-py3-none-any.whl", hash = "sha256:80bc33d715eec68e683e04298946d47d78c7739e79d808203df278ee8ef89428"},
{file = "pymdown_extensions-10.13.tar.gz", hash = "sha256:e0b351494dc0d8d14a1f52b39b1499a00ef1566b4ba23dc74f1eba75c736f5dd"},
{file = "pymdown_extensions-10.14-py3-none-any.whl", hash = "sha256:202481f716cc8250e4be8fce997781ebf7917701b59652458ee47f2401f818b5"},
{file = "pymdown_extensions-10.14.tar.gz", hash = "sha256:741bd7c4ff961ba40b7528d32284c53bc436b8b1645e8e37c3e57770b8700a34"},
]
[package.dependencies]
@@ -1283,7 +1283,7 @@ markdown = ">=3.6"
pyyaml = "*"
[package.extras]
extra = ["pygments (>=2.12)"]
extra = ["pygments (>=2.19.1)"]
[[package]]
name = "python-dateutil"
@@ -1603,13 +1603,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "virtualenv"
version = "20.28.0"
version = "20.28.1"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.8"
files = [
{file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"},
{file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"},
{file = "virtualenv-20.28.1-py3-none-any.whl", hash = "sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb"},
{file = "virtualenv-20.28.1.tar.gz", hash = "sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329"},
]
[package.dependencies]

View File

@@ -308,6 +308,12 @@ class FanModeConfig(MinerConfigOption):
if "minimumRequiredFans" in keys:
conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"])
return cls.manual(**conf)
if "disabled" in keys:
conf = {}
if "fanSpeedRatio" in temperature_conf["disabled"].keys():
conf["speed"] = int(temperature_conf["disabled"]["fanSpeedRatio"])
return cls.manual(**conf)
return cls.default()
@classmethod
def from_auradine(cls, web_fan: dict):

View File

@@ -364,7 +364,7 @@ class PoolGroup(MinerConfigValue):
if toml_group_conf.get("pool") is not None:
return cls(
name=toml_group_conf["name"],
quota=toml_group_conf.get("quota"),
quota=toml_group_conf.get("quota", 1),
pools=[Pool.from_bosminer(p) for p in toml_group_conf["pool"]],
)
return cls()

View File

@@ -14,6 +14,10 @@ BITAXE_DATA_LOC = DataLocations(
"_get_hashrate",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[WebAPICommand("web_system_info", "system/info")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[WebAPICommand("web_system_info", "system/info")],
@@ -100,6 +104,29 @@ class BitAxe(BaseMiner):
except KeyError:
pass
async def _get_expected_hashrate(
self, web_system_info: dict = None
) -> Optional[AlgoHashRate]:
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:
expected_hashrate = (
web_system_info.get("smallCoreCount")
* web_system_info.get("asicCount")
* web_system_info.get("frequency")
)
return self.algo.hashrate(
rate=float(expected_hashrate), unit=self.algo.unit.MH
).into(self.algo.unit.default)
except KeyError:
pass
async def _get_uptime(self, web_system_info: dict = None) -> Optional[int]:
if web_system_info is None:
try:

View File

@@ -160,6 +160,44 @@ class LUXMiner(LuxOSFirmware):
return False
async def atm_enabled(self) -> Optional[bool]:
try:
result = await self.rpc.atm()
return result["ATM"][0]["Enabled"]
except (APIError, LookupError):
pass
async def set_power_limit(self, wattage: int) -> bool:
config = await self.get_config()
valid_presets = {
preset.name: preset.power
for preset in config.mining_mode.available_presets
if preset.power <= wattage
}
# Set power to highest preset <= wattage
# If ATM enabled, must disable it before setting power limit
new_preset = max(valid_presets, key=valid_presets.get)
re_enable_atm = False
try:
if await self.atm_enabled():
re_enable_atm = True
await self.rpc.atmset("enabled=false")
result = await self.rpc.profileset(new_preset)
if re_enable_atm:
await self.rpc.atmset("enabled=true")
except APIError:
raise
except Exception as e:
logging.warning(f"{self} - Failed to set power limit: {e}")
return False
if result["PROFILE"][0]["Profile"] == new_preset:
return True
else:
return False
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################

View File

@@ -137,6 +137,80 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
"""
return await self.send_command("asccount")
async def atm(self) -> dict:
"""Get data for Advanced Thermal Management (ATM) configuration.
<details>
<summary>Expand</summary>
Returns:
A dictionary containing ATM configuration data:
- ATM: List containing a configuration object with:
- Enabled: Boolean indicating if ATM is enabled
- MaxProfile: Maximum frequency profile (e.g., "395MHz")
- MinProfile: Minimum frequency profile (e.g., "145MHz")
- PostRampMinutes: Minutes before ATM starts working after ramping
- StartupMinutes: Minutes before ATM starts working at systm startup
- TempWindow: Temperature window, before "Hot" in which ATM will change profiles
- STATUS: List containing a status object with:
- Code: Status code (e.g., 339)
- Description: Miner version
- Msg: Status message "ATM configuration values"
- STATUS: Status indicator
- When: Timestamp
</details>
"""
return await self.send_command("atm")
async def atmset(
self,
enabled: bool = None,
startup_minutes: int = None,
post_ramp_minutes: int = None,
temp_window: int = None,
min_profile: str = None,
max_profile: str = None,
prevent_oc: bool = None,
) -> dict:
"""Sets the ATM configuration.
<details>
<summary>Expand</summary>
Parameters:
enabled: Enable or disable ATM
startup_minutes: Minimum time (minutes) before ATM starts at system startup
post_ramp_minutes: Minimum time (minutes) before ATM starts after ramping
temp_window: Number of degrees below "Hot" temperature where ATM begins adjusting profiles
min_profile: Lowest profile to use (e.g. "145MHz", "+1", "-2"). Empty string for unbounded
max_profile: Highest profile to use (e.g. "395MHz", "+1", "-2")
prevent_oc: When turning off ATM, revert to default profile if in higher profile
Returns:
A dictionary containing status information about the ATM configuration update:
- STATUS: List containing a status object with:
- Code: Status code (e.g., 340)
- Description: Miner version
- Msg: Confirmation message "Advanced Thermal Management configuration updated"
- STATUS: Status indicator
- When: Timestamp
</details>
"""
atmset_data = []
if enabled is not None:
atmset_data.append(f"enabled={str(enabled).lower()}")
if startup_minutes is not None:
atmset_data.append(f"startup_minutes={startup_minutes}")
if post_ramp_minutes is not None:
atmset_data.append(f"post_ramp_minutes={post_ramp_minutes}")
if temp_window is not None:
atmset_data.append(f"temp_window={temp_window}")
if min_profile is not None:
atmset_data.append(f"min_profile={min_profile}")
if max_profile is not None:
atmset_data.append(f"max_profile={max_profile}")
if prevent_oc is not None:
atmset_data.append(f"prevent_oc={str(prevent_oc).lower()}")
return await self.send_privileged_command("atmset", *atmset_data)
async def check(self, command: str) -> dict:
"""Check if the command `command` exists in LUXMiner.
<details>
@@ -556,20 +630,19 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
"""
return await self.send_command("profiles")
async def profileset(self, board_n: int, profile: str) -> dict:
"""Set active profile for a board.
async def profileset(self, profile: str) -> dict:
"""Set active profile for the system.
<details>
<summary>Expand</summary>
Parameters:
board_n: The board to set the profile on.
profile: The profile name to use.
Returns:
A confirmation of setting the profile on board_n.
A confirmation of setting the profile.
</details>
"""
return await self.send_privileged_command("profileset", board_n, profile)
return await self.send_privileged_command("profileset", profile)
async def reboot(self, board_n: int, delay_s: int = None) -> dict:
"""Reboot a board.

View File

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