fix(antminer): read power draw from the new_api stats payload - #325
Merged
Conversation
b-rowan
requested changes
Aug 13, 2026
b-rowan
left a comment
Member
There was a problem hiding this comment.
I think using the RPC endpoint is preferable (it should be faster than the web and doesn't need auth).
…meter`
`send_rpc_command` wrapped every parameter value in cgminer's `parameter`
argument. Bitmain's `new_api` is not a `parameter` — it is a top-level flag
alongside `command`, and the firmware ignores it when nested, answering with
the legacy payload instead.
Every `new_api` helper on this client was affected: `stats`, `summary`,
`pools`, `rate`, `warning` and `reload` all requested the new API and silently
received the old one. The failure is invisible to a caller — the response is a
well-formed `STATUS: "S"` body, just the wrong shape.
Verified against L9 and L11 on 86.48-2.0.0:
{"command":"stats","new_api":true} -> STATS[1], "stats"
{"command":"stats","parameter":{"new_api":true}} -> STATS[2], "CGMiner stats"
The second is byte-identical to sending no parameter at all.
Object parameters now merge at the top level; scalars keep the `parameter`
wrapper, preserving cgminer's own convention for commands like `switchpool`
that take a pool index. Within this backend every parameter is currently a
`new_api` object, so nothing relied on the old wrapping.
Request construction is extracted into `build_rpc_request` so the wire format
is unit-testable without a socket.
`wattage` is always `None` on newer stock firmware. `DataField::Wattage` is
sourced from the legacy RPC `stats` payload, which carries no power reading on
these generations.
The reading is present in the `new_api` variant of `stats`. That is a
different payload: its `STATS` array holds a single element, so the value sits
at `/STATS/0` rather than the `/STATS/1` the legacy source uses. The legacy
location is kept and tried first, so firmware that does report power there is
unaffected.
The key name varies by model on identical firmware — the L9 reports `power`,
the L11 `watt` — so `watt` is accepted alongside the existing `power` /
`Power` / `chain_power`.
Measured against live hardware:
L11 None -> 3651 W L9 None -> 3357 W
L11 None -> 3636 W L9 None -> 3353 W
L9 None -> 3379 W
An idle unit reports its true low draw rather than falling back to absent, so
a genuine zero stays distinguishable from a missing reading. Models whose
firmware exposes no power at all (T21, S21 Hydro, S21+ Hydro) continue to
report nothing, and units that do not honour `new_api` still receive the
legacy payload.
Applied to both `v2020` and `v2023_07`.
cryptographicturk
force-pushed
the
pr/antminer-wattage
branch
from
August 13, 2026 20:14
afbc598 to
4a0dd07
Compare
new_api stats payload
Contributor
Author
|
I updated it after submitting another PR to fix an underlying issue that prevented this solution from working. |
b-rowan
requested changes
Aug 17, 2026
…hape Review feedback: check for the `new_api` key directly instead of inferring top-level placement from the parameter being a JSON object. `build_rpc_request` now removes `new_api` from the parameter object by name and places it alongside `command`. Every other parameter — object or scalar — keeps cgminer's `parameter` wrapper, so the generic object rule no longer applies to payloads that never needed it. The check stays inside `build_rpc_request` because `new_api` does not only arrive through the typed helpers on this client: `APIClient::get_api_result` forwards the parameters of a `MinerCommand::RPC` straight through, which is the path a `GetDataLocations` entry takes. A dedicated `flags` argument on the RPC command would be the place for further Bitmain extensions, but it means changing `MinerCommand` in core, so it is left for when a second flag exists. Tests renamed to describe the key rather than the shape, and two added: a non-`new_api` object keeps the wrapper, and `new_api` is lifted out of a larger object while the remaining keys stay wrapped.
Review feedback: the payload difference is implied by the flag itself, so the explanation of why the reading sits at `/STATS/0` is not needed. The note on why the command cannot be a `const` stays.
b-rowan
approved these changes
Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
wattageis alwaysNoneon newer stock firmware.DataField::Wattageissourced from the legacy RPC
statspayload, which carries no power reading onthese generations.
Where the reading lives
The
new_apivariant ofstatsreports it. That is a different payload to thelegacy one — its
STATSarray holds a single element, so the value sits at/STATS/0rather than the/STATS/1the legacy source uses:The change
stats+new_apias a second source forDataField::Wattage, read at/STATS/0.wattkey alongside the existingpower/Power/chain_power. The spelling varies by model on identical firmware: the L9reports
power, the L11watt.The legacy location is kept and tried first, so firmware that does report power
there is unaffected. Applied to both
v2020andv2023_07.Measured
Against live hardware, all previously
None:An idle unit reports its true low draw rather than falling back to absent, so a
genuine zero stays distinguishable from a missing reading.
No regressions in the groups that cannot benefit: T21, S21 Hydro and S21+ Hydro
expose no power draw on any transport and continue to report nothing, and older
units that do not honour
new_apistill receive the legacy payload exactly asbefore.
The two payloads share only
fan_numandrate_30m, identical in both, somerging them into one field cannot silently clobber a value.
Verification
cargo fmt --all -- --check— cleancargo clippy -p asic-rs-firmwares-antminer --all-targets— cleancargo test -p asic-rs-firmwares-antminer— 20 passedcargo test --workspace— no failures