From 63ac5639f5ce50f1376ab2467a811c2013114942 Mon Sep 17 00:00:00 2001 From: Adrian <33915735+cryptographicturk@users.noreply.github.com> Date: Wed, 12 Aug 2026 16:24:30 -0700 Subject: [PATCH] fix(antminer): read chain rate and board temps on newer stock firmware Two parsing gaps on AntMiner stock firmware 86.48-2.0.0 (seen on L9 and L11), both of which left per-board fields null: - chain_rate{idx} is emitted as a JSON number on this generation, but was read with as_str(), so per-board hashrate was always None. Now accepts either representation. - temp_pcb{idx} does not exist on this generation; the firmware splits the reading into temp_in_pcb_{idx} / temp_out_pcb_{idx}. Falls back to the outlet reading when the combined key is absent. Measured against live hardware, per board: before: temp=None hashrate=None after: temp=53C hashrate=6.80 GH/s average_temperature also populates as a result, since it is derived from the per-board readings. Applies to both the v2020 and v2023_07 backends, which share this parser. --- .../antminer/src/backends/v2020/mod.rs | 22 ++++++++++++++++--- .../antminer/src/backends/v2023_07/mod.rs | 22 ++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/asic-rs-firmwares/antminer/src/backends/v2020/mod.rs b/asic-rs-firmwares/antminer/src/backends/v2020/mod.rs index 5ee247d8..f83ab721 100644 --- a/asic-rs-firmwares/antminer/src/backends/v2020/mod.rs +++ b/asic-rs-firmwares/antminer/src/backends/v2020/mod.rs @@ -216,6 +216,14 @@ impl AntMinerV2020 { } } + /// Read a numeric field that some firmware generations emit as a JSON + /// number and others as a quoted string. + fn parse_f64_field(value: &Value) -> Option { + value + .as_f64() + .or_else(|| value.as_str().and_then(|s| s.trim().parse::().ok())) + } + fn parse_temp_string(temp_str: &str) -> Option { let temps: Vec = temp_str .split('-') @@ -609,8 +617,7 @@ impl GetHashboards for AntMinerV2020 { board.hashrate = stats_data .get(&format!("chain_rate{idx}")) - .and_then(|v| v.as_str()) - .and_then(|s| s.parse::().ok()) + .and_then(Self::parse_f64_field) .map(|r| { HashRate { value: r, @@ -620,10 +627,19 @@ impl GetHashboards for AntMinerV2020 { .as_unit(HashRateUnit::default()) }); + // `temp_pcb{idx}` on older generations; newer ones split the + // reading into inlet/outlet pairs and omit the combined key. board.board_temperature = stats_data .get(&format!("temp_pcb{idx}")) .and_then(|v| v.as_str()) - .and_then(Self::parse_temp_string); + .and_then(Self::parse_temp_string) + .or_else(|| { + stats_data + .get(&format!("temp_out_pcb_{idx}")) + .and_then(Self::parse_f64_field) + .filter(|&t| t > 0.0) + .map(Temperature::from_celsius) + }); board.working_chips = stats_data .get(&format!("chain_acn{idx}")) diff --git a/asic-rs-firmwares/antminer/src/backends/v2023_07/mod.rs b/asic-rs-firmwares/antminer/src/backends/v2023_07/mod.rs index 0a1d5552..71b5b6ce 100644 --- a/asic-rs-firmwares/antminer/src/backends/v2023_07/mod.rs +++ b/asic-rs-firmwares/antminer/src/backends/v2023_07/mod.rs @@ -192,6 +192,14 @@ impl AntMinerV202307 { } } + /// Read a numeric field that some firmware generations emit as a JSON + /// number and others as a quoted string. + fn parse_f64_field(value: &Value) -> Option { + value + .as_f64() + .or_else(|| value.as_str().and_then(|s| s.trim().parse::().ok())) + } + fn parse_temp_string(temp_str: &str) -> Option { let temps: Vec = temp_str .split('-') @@ -585,8 +593,7 @@ impl GetHashboards for AntMinerV202307 { board.hashrate = stats_data .get(&format!("chain_rate{idx}")) - .and_then(|v| v.as_str()) - .and_then(|s| s.parse::().ok()) + .and_then(Self::parse_f64_field) .map(|r| { HashRate { value: r, @@ -596,10 +603,19 @@ impl GetHashboards for AntMinerV202307 { .as_unit(HashRateUnit::default()) }); + // `temp_pcb{idx}` on older generations; newer ones split the + // reading into inlet/outlet pairs and omit the combined key. board.board_temperature = stats_data .get(&format!("temp_pcb{idx}")) .and_then(|v| v.as_str()) - .and_then(Self::parse_temp_string); + .and_then(Self::parse_temp_string) + .or_else(|| { + stats_data + .get(&format!("temp_out_pcb_{idx}")) + .and_then(Self::parse_f64_field) + .filter(|&t| t > 0.0) + .map(Temperature::from_celsius) + }); board.working_chips = stats_data .get(&format!("chain_acn{idx}"))