Compare commits

...

5 Commits

Author SHA1 Message Date
Brett Rowan
75056cfff5 version: bump version number 2025-09-19 14:18:36 -06:00
James Hilliard
7fbcb0dbd2 Fix race condition in BOSer multicommand causing CancelledError
The multicommand method was double-awaiting tasks - first via
asyncio.gather() with return_exceptions=True, then trying to await
the same tasks again. This caused CancelledError when gRPC connections
were lost.

Changed to properly use the results from gather() instead of
re-awaiting completed tasks, preventing the race condition and
properly handling exceptions.

Fixes StreamTerminatedError occurring in pyasic/web/braiins_os/boser.py:91
2025-09-19 14:17:59 -06:00
Brett Rowan
7329aeace2 version: bump version number 2025-09-17 19:18:51 -06:00
Brett Rowan
e8c3953106 bug: fix btminer V3 password 2025-09-17 19:18:27 -06:00
James Hilliard
a1a7562bdb Handle invalid unicode in json response 2025-09-17 19:11:31 -06:00
9 changed files with 80 additions and 28 deletions

View File

@@ -0,0 +1,16 @@
# pyasic
## Q Models
## Avalon Q Home (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.avalonminer.cgminer.Q.Q.CGMinerAvalonQHome
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -1,5 +1,5 @@
# pyasic
## Byte Models
## byte Models
## Byte (Stock)
@@ -8,7 +8,7 @@
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.goldshell.bfgminer.Byte.Byte.GoldshellByte
::: pyasic.miners.goldshell.bfgminer.byte.byte.GoldshellByte
handler: python
options:
show_root_heading: false

View File

@@ -0,0 +1,16 @@
# pyasic
## mini_doge Models
## Mini Doge (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.goldshell.bfgminer.mini_doge.mini_doge.GoldshellMiniDoge
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -0,0 +1,16 @@
# pyasic
## ALX Models
## AL3 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.iceriver.iceminer.ALX.AL3.IceRiverAL3
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -603,12 +603,6 @@ details {
<details>
<summary>Stock Firmware Goldshells:</summary>
<ul>
<details>
<summary>Mini Doge Series:</summary>
<ul>
<li><a href="../goldshell/MiniDoge#mini-doge-stock">Mini Doge (Stock)</a></li>
</ul>
</details>
<details>
<summary>X5 Series:</summary>
<ul>
@@ -631,9 +625,15 @@ details {
</ul>
</details>
<details>
<summary>Byte Series:</summary>
<summary>byte Series:</summary>
<ul>
<li><a href="../goldshell/Byte#byte-stock">Byte (Stock)</a></li>
<li><a href="../goldshell/byte#byte-stock">Byte (Stock)</a></li>
</ul>
</details>
<details>
<summary>mini_doge Series:</summary>
<ul>
<li><a href="../goldshell/mini_doge#mini-doge-stock">Mini Doge (Stock)</a></li>
</ul>
</details>
</ul>

View File

@@ -253,10 +253,10 @@ If you are sure you want to use this command please use API.send_command("{comma
# some json from the API returns with a null byte (\x00) on the end
if data.endswith(b"\x00"):
# handle the null byte
str_data = data.decode("utf-8")[:-1]
str_data = data.decode("utf-8", errors="replace")[:-1]
else:
# no null byte
str_data = data.decode("utf-8")
str_data = data.decode("utf-8", errors="replace")
# fix an error with a btminer return having an extra comma that breaks json.loads()
str_data = str_data.replace(",}", "}")
# fix an error with a btminer return having a newline that breaks json.loads()

View File

@@ -252,13 +252,13 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
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
)
# 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,
@@ -293,6 +293,7 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
try:
data = parse_btminer_priviledge_data(self.token, data)
print(data)
except Exception as e:
logging.info(f"{str(self.ip)}: {e}")
@@ -1109,6 +1110,7 @@ class BTMinerV3RPCAPI(BaseMinerRPCAPI):
super().__init__(ip, port, api_ver=api_ver)
self.salt = None
self.pwd = "super"
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
"""Creates and sends multiple commands as one command to the miner.
@@ -1141,9 +1143,11 @@ class BTMinerV3RPCAPI(BaseMinerRPCAPI):
token_hashed = bytearray(
base64.b64encode(hashlib.sha256(token_str.encode("utf-8")).digest())
)
token_hashed[8] = 0
b_arr = bytearray(token_hashed)
b_arr[8] = 0
str_token = b_arr.split(b"\x00")[0].decode("utf-8")
cmd["account"] = "super"
cmd["token"] = token_hashed.decode("ascii")
cmd["token"] = str_token
# send the command
ser = json.dumps(cmd).encode("utf-8")

View File

@@ -84,13 +84,13 @@ class BOSerWebAPI(BaseWebAPI):
except AttributeError:
pass
await asyncio.gather(*[t for t in tasks.values()], return_exceptions=True)
results = await asyncio.gather(
*[t for t in tasks.values()], return_exceptions=True
)
for cmd in tasks:
try:
result[cmd] = await tasks[cmd]
except (GRPCError, APIError, ConnectionError):
pass
for cmd, task_result in zip(tasks.keys(), results):
if not isinstance(task_result, (GRPCError, APIError, ConnectionError)):
result[cmd] = task_result
return result

View File

@@ -1,6 +1,6 @@
[project]
name = "pyasic"
version = "0.76.6"
version = "0.76.8"
description = "A simplified and standardized interface for Bitcoin ASICs."
authors = [{name = "UpstreamData", email = "brett@upstreamdata.ca"}]