Skip to content

Commit 7814f51

Browse files
ankitgoswamiclaude
andcommitted
fix(whatsminer): correct inverted is_mining logic across all backends
V1/V2: The `btmineroff`/`mineroff` field means "miner is off", so "true" means mining is OFF. The old code checked `l != "false"` which returned true (mining) when the miner was actually paused. Also added a fallback extractor at `/Msg/mineroff` for newer firmware responding to the legacy API. V3: Had no implementation (always returned true). Now extracts the `working` field from `get.device.info` which directly indicates whether the miner is active. Verified against M60SVK40 hardware: pause() correctly flips is_mining to false, resume() flips it back to true. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 501bcc9 commit 7814f51

3 files changed

Lines changed: 98 additions & 21 deletions

File tree

asic-rs-firmwares/whatsminer/src/backends/v1/mod.rs

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,24 @@ impl GetDataLocations for WhatsMinerV1 {
218218
tag: None,
219219
},
220220
)],
221-
DataField::IsMining => vec![(
222-
RPC_STATUS,
223-
DataExtractor {
224-
func: get_by_pointer,
225-
key: Some("/SUMMARY/0/btmineroff"),
226-
tag: None,
227-
},
228-
)],
221+
DataField::IsMining => vec![
222+
(
223+
RPC_STATUS,
224+
DataExtractor {
225+
func: get_by_pointer,
226+
key: Some("/SUMMARY/0/btmineroff"),
227+
tag: None,
228+
},
229+
),
230+
(
231+
RPC_STATUS,
232+
DataExtractor {
233+
func: get_by_pointer,
234+
key: Some("/Msg/mineroff"),
235+
tag: None,
236+
},
237+
),
238+
],
229239
DataField::Messages => vec![(
230240
RPC_SUMMARY,
231241
DataExtractor {
@@ -473,8 +483,9 @@ impl GetUptime for WhatsMinerV1 {
473483
}
474484
impl GetIsMining for WhatsMinerV1 {
475485
fn parse_is_mining(&self, data: &HashMap<DataField, Value>) -> bool {
476-
data.extract_map::<String, _>(DataField::IsMining, |l| l != "false")
477-
.unwrap_or(true)
486+
// Raw field is "mineroff" / "btmineroff": "true" means mining is OFF.
487+
let miner_off = data.extract::<String>(DataField::IsMining);
488+
miner_off.as_deref() != Some("true")
478489
}
479490
}
480491
impl GetPools for WhatsMinerV1 {
@@ -692,4 +703,45 @@ mod tests {
692703

693704
Ok(())
694705
}
706+
707+
#[test]
708+
fn test_parse_is_mining_when_miner_off() {
709+
// Arrange - mineroff="true" means the miner is off
710+
let miner = WhatsMinerV1::new(IpAddr::from([127, 0, 0, 1]), WhatsMinerModel::M20SV10);
711+
let mut data = HashMap::new();
712+
data.insert(DataField::IsMining, Value::String("true".to_string()));
713+
714+
// Act
715+
let is_mining = miner.parse_is_mining(&data);
716+
717+
// Assert
718+
assert!(!is_mining);
719+
}
720+
721+
#[test]
722+
fn test_parse_is_mining_when_miner_on() {
723+
// Arrange - mineroff="false" means the miner is running
724+
let miner = WhatsMinerV1::new(IpAddr::from([127, 0, 0, 1]), WhatsMinerModel::M20SV10);
725+
let mut data = HashMap::new();
726+
data.insert(DataField::IsMining, Value::String("false".to_string()));
727+
728+
// Act
729+
let is_mining = miner.parse_is_mining(&data);
730+
731+
// Assert
732+
assert!(is_mining);
733+
}
734+
735+
#[test]
736+
fn test_parse_is_mining_missing_defaults_to_mining() {
737+
// Arrange - no status data available
738+
let miner = WhatsMinerV1::new(IpAddr::from([127, 0, 0, 1]), WhatsMinerModel::M20SV10);
739+
let data = HashMap::new();
740+
741+
// Act
742+
let is_mining = miner.parse_is_mining(&data);
743+
744+
// Assert
745+
assert!(is_mining);
746+
}
695747
}

asic-rs-firmwares/whatsminer/src/backends/v2/mod.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,24 @@ impl GetDataLocations for WhatsMinerV2 {
243243
tag: None,
244244
},
245245
)],
246-
DataField::IsMining => vec![(
247-
RPC_STATUS,
248-
DataExtractor {
249-
func: get_by_pointer,
250-
key: Some("/SUMMARY/0/btmineroff"),
251-
tag: None,
252-
},
253-
)],
246+
DataField::IsMining => vec![
247+
(
248+
RPC_STATUS,
249+
DataExtractor {
250+
func: get_by_pointer,
251+
key: Some("/SUMMARY/0/btmineroff"),
252+
tag: None,
253+
},
254+
),
255+
(
256+
RPC_STATUS,
257+
DataExtractor {
258+
func: get_by_pointer,
259+
key: Some("/Msg/mineroff"),
260+
tag: None,
261+
},
262+
),
263+
],
254264
DataField::Messages => vec![(
255265
RPC_GET_ERROR_CODE,
256266
DataExtractor {
@@ -507,8 +517,9 @@ impl GetUptime for WhatsMinerV2 {
507517
}
508518
impl GetIsMining for WhatsMinerV2 {
509519
fn parse_is_mining(&self, data: &HashMap<DataField, Value>) -> bool {
510-
data.extract_map::<String, _>(DataField::IsMining, |l| l != "false")
511-
.unwrap_or(true)
520+
// Raw field is "mineroff" / "btmineroff": "true" means mining is OFF.
521+
let miner_off = data.extract::<String>(DataField::IsMining);
522+
miner_off.as_deref() != Some("true")
512523
}
513524
}
514525
impl GetPools for WhatsMinerV2 {

asic-rs-firmwares/whatsminer/src/backends/v3/mod.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,14 @@ impl GetDataLocations for WhatsMinerV3 {
244244
tag: None,
245245
},
246246
)],
247+
DataField::IsMining => vec![(
248+
RPC_GET_DEVICE_INFO,
249+
DataExtractor {
250+
func: get_by_pointer,
251+
key: Some("/msg/miner/working"),
252+
tag: None,
253+
},
254+
)],
247255
_ => vec![],
248256
}
249257
}
@@ -449,7 +457,13 @@ impl GetUptime for WhatsMinerV3 {
449457
data.extract_map::<u64, _>(DataField::Uptime, Duration::from_secs)
450458
}
451459
}
452-
impl GetIsMining for WhatsMinerV3 {}
460+
impl GetIsMining for WhatsMinerV3 {
461+
fn parse_is_mining(&self, data: &HashMap<DataField, Value>) -> bool {
462+
// Raw field is "working": "true" means mining is ON.
463+
let working = data.extract::<String>(DataField::IsMining);
464+
working.as_deref() != Some("false")
465+
}
466+
}
453467
impl GetPools for WhatsMinerV3 {
454468
fn parse_pools(&self, data: &HashMap<DataField, Value>) -> Vec<PoolGroupData> {
455469
let mut pools: Vec<PoolData> = Vec::new();

0 commit comments

Comments
 (0)