From 93fbf5abbe6348ffbd991f513c9378686e43d1ba Mon Sep 17 00:00:00 2001 From: Brett Rowan <121075405+b-rowan@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:26:33 -0600 Subject: [PATCH] feat: add `Validate` trait and `revalidate` function --- asic-rs-core/src/traits/firmware.rs | 2 +- asic-rs-core/src/traits/miner.rs | 59 ++++++++++++++++++- .../antminer/src/backends/mod.rs | 12 ++-- .../antminer/src/backends/v2020/mod.rs | 9 +++ .../antminer/src/backends/v2023_07/mod.rs | 9 +++ asic-rs-firmwares/antminer/src/firmware.rs | 2 +- .../auradine/src/backends/v1/mod.rs | 4 ++ asic-rs-firmwares/auradine/src/firmware.rs | 2 +- .../avalonminer/src/backends/avalon_a/mod.rs | 4 ++ .../avalonminer/src/backends/avalon_q/mod.rs | 4 ++ asic-rs-firmwares/avalonminer/src/firmware.rs | 2 +- asic-rs-firmwares/bitaxe/src/backends/mod.rs | 7 ++- .../bitaxe/src/backends/v2_0_0/mod.rs | 9 +++ .../bitaxe/src/backends/v2_0_0/web.rs | 6 +- .../bitaxe/src/backends/v2_9_0/mod.rs | 9 +++ asic-rs-firmwares/bitaxe/src/firmware.rs | 2 +- asic-rs-firmwares/braiins/src/backends/mod.rs | 22 ++++--- .../braiins/src/backends/v21_09/mod.rs | 9 +++ .../braiins/src/backends/v25_03/mod.rs | 9 +++ .../braiins/src/backends/v25_05/mod.rs | 9 +++ .../braiins/src/backends/v25_07/mod.rs | 9 +++ .../braiins/src/backends/v26_04/mod.rs | 9 +++ asic-rs-firmwares/braiins/src/firmware.rs | 2 +- asic-rs-firmwares/epic/src/backends/v1/mod.rs | 4 ++ asic-rs-firmwares/epic/src/backends/v1/web.rs | 2 +- asic-rs-firmwares/epic/src/firmware.rs | 2 +- .../futurebit/src/backends/v2/mod.rs | 4 ++ asic-rs-firmwares/futurebit/src/firmware.rs | 2 +- .../luxminer/src/backends/v1/mod.rs | 4 ++ asic-rs-firmwares/luxminer/src/firmware.rs | 2 +- .../marathon/src/backends/v1/mod.rs | 4 ++ asic-rs-firmwares/marathon/src/firmware.rs | 2 +- .../nerdaxe/src/backends/v1/mod.rs | 4 ++ .../nerdaxe/src/backends/v1/web.rs | 6 +- asic-rs-firmwares/nerdaxe/src/firmware.rs | 2 +- .../proto/src/backends/v1/mod.rs | 4 ++ .../sealminer/src/backends/v2025/mod.rs | 4 ++ asic-rs-firmwares/sealminer/src/firmware.rs | 2 +- asic-rs-firmwares/vnish/src/backends/mod.rs | 16 ++--- .../vnish/src/backends/v1_2_0/mod.rs | 9 +++ .../vnish/src/backends/v1_2_0/web.rs | 2 +- .../vnish/src/backends/v1_3_0/mod.rs | 9 +++ .../vnish/src/backends/v1_3_0/web.rs | 2 +- asic-rs-firmwares/vnish/src/firmware.rs | 2 +- .../volcminer/src/backends/v1/mod.rs | 4 ++ asic-rs-firmwares/volcminer/src/firmware.rs | 2 +- .../whatsminer/src/backends/mod.rs | 17 +++--- .../whatsminer/src/backends/v1/mod.rs | 9 +++ .../whatsminer/src/backends/v2/mod.rs | 9 +++ .../whatsminer/src/backends/v3/mod.rs | 9 +++ asic-rs-firmwares/whatsminer/src/firmware.rs | 2 +- docs/api.md | 4 ++ python/pyasic_rs/asic_rs.pyi | 1 + src/python/miner.rs | 12 ++++ 54 files changed, 309 insertions(+), 59 deletions(-) diff --git a/asic-rs-core/src/traits/firmware.rs b/asic-rs-core/src/traits/firmware.rs index 9cf85b43..bed9d48e 100644 --- a/asic-rs-core/src/traits/firmware.rs +++ b/asic-rs-core/src/traits/firmware.rs @@ -9,6 +9,6 @@ use crate::{ #[async_trait] pub trait MinerFirmware: ToString + DiscoveryCommands { - async fn get_model(ip: IpAddr) -> Result; + async fn get_model(ip: IpAddr) -> Result; async fn get_version(ip: IpAddr) -> Option; } diff --git a/asic-rs-core/src/traits/miner.rs b/asic-rs-core/src/traits/miner.rs index 5cf340a0..0d1a170d 100644 --- a/asic-rs-core/src/traits/miner.rs +++ b/asic-rs-core/src/traits/miner.rs @@ -31,6 +31,7 @@ use crate::{ miner::{MinerData, TuningTarget}, pool::PoolGroupData, }, + traits::firmware::MinerFirmware, traits::model::MinerModel, util::unix_timestamp_secs, }; @@ -42,15 +43,29 @@ pub trait MinerConstructor { fn new(ip: IpAddr, model: impl MinerModel, version: Option) -> Box; } +#[async_trait] pub trait Miner: GetMinerData + HasMinerControl + SupportsConfigs + UpgradeFirmware + HasAuth + HasDefaultAuth { + async fn revalidate(&self) -> anyhow::Result; } +#[async_trait] impl< - T: GetMinerData + HasMinerControl + SupportsConfigs + UpgradeFirmware + HasAuth + HasDefaultAuth, + T: GetMinerData + + HasMinerControl + + SupportsConfigs + + UpgradeFirmware + + Validate + + HasAuth + + HasDefaultAuth, > Miner for T { + // Needs to be implemented here because otherwise Miner becomes + // dyn incompatible. + async fn revalidate(&self) -> anyhow::Result { + ::revalidate(self).await + } } pub trait HasMinerControl: @@ -384,6 +399,48 @@ pub trait GraphQLClient: APIClient { ) -> anyhow::Result; } +#[async_trait] +pub trait Validate: GetIP + GetDeviceInfo + Send + Sync { + type Firmware: MinerFirmware + Default + Send + Sync + 'static; + + fn validate(_version: Option<&semver::Version>) -> bool + where + Self: Sized, + { + true + } + + /// Re-run this backend's firmware discovery checks against the miner IP. + /// + /// Returns `true` when the device still identifies as the same make, model, + /// firmware family, and backend version range. Returns `false` for offline + /// devices, unsupported responses, or devices that would no longer be valid + /// for this backend. + async fn revalidate(&self) -> anyhow::Result + where + Self: Sized, + { + let expected = self.get_device_info(); + + let Ok(model) = ::get_model(self.get_ip()).await else { + return Ok(false); + }; + + if model.make_name() != expected.make { + return Ok(false); + } + if model.to_string() != expected.model { + return Ok(false); + } + if Self::Firmware::default().to_string() != expected.firmware { + return Ok(false); + } + + let version = ::get_version(self.get_ip()).await; + Ok(Self::validate(version.as_ref())) + } +} + // Data traits pub trait GetIP: Send + Sync { /// Returns the IP address of the miner. diff --git a/asic-rs-firmwares/antminer/src/backends/mod.rs b/asic-rs-firmwares/antminer/src/backends/mod.rs index 10b34e2e..818d61f3 100644 --- a/asic-rs-firmwares/antminer/src/backends/mod.rs +++ b/asic-rs-firmwares/antminer/src/backends/mod.rs @@ -4,10 +4,9 @@ pub mod v2023_07; use std::net::IpAddr; use asic_rs_core::traits::{ - miner::{Miner, MinerConstructor}, + miner::{Miner, MinerConstructor, Validate}, model::MinerModel, }; -use semver::Version; use v2020::AntMinerV2020; use v2023_07::AntMinerV202307; @@ -16,9 +15,12 @@ pub struct AntMiner; impl MinerConstructor for AntMiner { #[allow(clippy::new_ret_no_self)] fn new(ip: IpAddr, model: impl MinerModel, version: Option) -> Box { - match version { - Some(ref v) if *v < Version::new(2023, 7, 0) => Box::new(AntMinerV2020::new(ip, model)), - _ => Box::new(AntMinerV202307::new(ip, model)), + if AntMinerV2020::validate(version.as_ref()) { + Box::new(AntMinerV2020::new(ip, model)) + } else if AntMinerV202307::validate(version.as_ref()) { + Box::new(AntMinerV202307::new(ip, model)) + } else { + Box::new(AntMinerV202307::new(ip, model)) } } } diff --git a/asic-rs-firmwares/antminer/src/backends/v2020/mod.rs b/asic-rs-firmwares/antminer/src/backends/v2020/mod.rs index ef9dc531..08ee0a7d 100644 --- a/asic-rs-firmwares/antminer/src/backends/v2020/mod.rs +++ b/asic-rs-firmwares/antminer/src/backends/v2020/mod.rs @@ -29,6 +29,7 @@ use async_trait::async_trait; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature}; use rpc::AntMinerRPCAPI; +use semver::Version; use serde_json::{Value, json}; use web::AntMinerWebAPI; @@ -1157,6 +1158,14 @@ impl HasAuth for AntMinerV2020 { } } +impl Validate for AntMinerV2020 { + type Firmware = AntMinerStockFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v < Version::new(2023, 7, 0)) + } +} + #[async_trait] impl SupportsTuningConfig for AntMinerV2020 { async fn set_tuning_config( 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 0791342e..ad53fadc 100644 --- a/asic-rs-firmwares/antminer/src/backends/v2023_07/mod.rs +++ b/asic-rs-firmwares/antminer/src/backends/v2023_07/mod.rs @@ -31,6 +31,7 @@ use async_trait::async_trait; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature}; use rpc::AntMinerRPCAPI; +use semver::Version; use serde_json::{Value, json}; use web::AntMinerWebAPI; @@ -1084,6 +1085,14 @@ impl HasAuth for AntMinerV202307 { } } +impl Validate for AntMinerV202307 { + type Firmware = AntMinerStockFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v >= Version::new(2023, 7, 0)) + } +} + #[async_trait] impl SupportsTuningConfig for AntMinerV202307 { async fn set_tuning_config( diff --git a/asic-rs-firmwares/antminer/src/firmware.rs b/asic-rs-firmwares/antminer/src/firmware.rs index 5e9b0814..514b2dad 100644 --- a/asic-rs-firmwares/antminer/src/firmware.rs +++ b/asic-rs-firmwares/antminer/src/firmware.rs @@ -146,7 +146,7 @@ async fn get_version_with_auth(ip: IpAddr, auth: &MinerAuth) -> Option Result { + async fn get_model(ip: IpAddr) -> Result { let default = crate::backends::v2020::AntMinerV2020::default_auth(); get_model_with_auth(ip, &default).await } diff --git a/asic-rs-firmwares/auradine/src/backends/v1/mod.rs b/asic-rs-firmwares/auradine/src/backends/v1/mod.rs index 9b049010..3bbc56bb 100644 --- a/asic-rs-firmwares/auradine/src/backends/v1/mod.rs +++ b/asic-rs-firmwares/auradine/src/backends/v1/mod.rs @@ -1407,6 +1407,10 @@ impl HasAuth for AuradineV1 { } } +impl Validate for AuradineV1 { + type Firmware = AuradineFirmware; +} + #[async_trait] impl SupportsTuningConfig for AuradineV1 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/auradine/src/firmware.rs b/asic-rs-firmwares/auradine/src/firmware.rs index f803361c..decc311b 100644 --- a/asic-rs-firmwares/auradine/src/firmware.rs +++ b/asic-rs-firmwares/auradine/src/firmware.rs @@ -57,7 +57,7 @@ fn parse_semver_like(version_str: &str) -> Option { #[async_trait] impl MinerFirmware for AuradineFirmware { - async fn get_model(ip: IpAddr) -> Result { + async fn get_model(ip: IpAddr) -> Result { let data = util::send_rpc_command(&ip, "devdetails") .await .ok_or(ModelSelectionError::NoModelResponse)?; diff --git a/asic-rs-firmwares/avalonminer/src/backends/avalon_a/mod.rs b/asic-rs-firmwares/avalonminer/src/backends/avalon_a/mod.rs index d9f6144c..be680b09 100644 --- a/asic-rs-firmwares/avalonminer/src/backends/avalon_a/mod.rs +++ b/asic-rs-firmwares/avalonminer/src/backends/avalon_a/mod.rs @@ -981,6 +981,10 @@ impl UpgradeFirmware for AvalonAMiner { impl HasAuth for AvalonAMiner {} impl HasDefaultAuth for AvalonAMiner {} +impl Validate for AvalonAMiner { + type Firmware = AvalonStockFirmware; +} + #[async_trait] impl SupportsTuningConfig for AvalonAMiner { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/avalonminer/src/backends/avalon_q/mod.rs b/asic-rs-firmwares/avalonminer/src/backends/avalon_q/mod.rs index feb495c5..a8c20dcb 100644 --- a/asic-rs-firmwares/avalonminer/src/backends/avalon_q/mod.rs +++ b/asic-rs-firmwares/avalonminer/src/backends/avalon_q/mod.rs @@ -865,6 +865,10 @@ impl UpgradeFirmware for AvalonQMiner { impl HasAuth for AvalonQMiner {} impl HasDefaultAuth for AvalonQMiner {} +impl Validate for AvalonQMiner { + type Firmware = AvalonStockFirmware; +} + #[async_trait] impl SupportsTuningConfig for AvalonQMiner { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/avalonminer/src/firmware.rs b/asic-rs-firmwares/avalonminer/src/firmware.rs index 5c96bd95..2ef74465 100644 --- a/asic-rs-firmwares/avalonminer/src/firmware.rs +++ b/asic-rs-firmwares/avalonminer/src/firmware.rs @@ -34,7 +34,7 @@ impl DiscoveryCommands for AvalonStockFirmware { #[async_trait] impl MinerFirmware for AvalonStockFirmware { - async fn get_model(ip: IpAddr) -> Result { + async fn get_model(ip: IpAddr) -> Result { let response = util::send_rpc_command(&ip, "version").await; match response { diff --git a/asic-rs-firmwares/bitaxe/src/backends/mod.rs b/asic-rs-firmwares/bitaxe/src/backends/mod.rs index 1d1f99e2..7162ed41 100644 --- a/asic-rs-firmwares/bitaxe/src/backends/mod.rs +++ b/asic-rs-firmwares/bitaxe/src/backends/mod.rs @@ -1,10 +1,9 @@ use std::net::IpAddr; use asic_rs_core::traits::{ - miner::{Miner, MinerConstructor}, + miner::{Miner, MinerConstructor, Validate}, model::MinerModel, }; -use semver::Version; pub use v2_0_0::Bitaxe200; pub use v2_9_0::Bitaxe290; @@ -16,8 +15,10 @@ pub struct Bitaxe; impl MinerConstructor for Bitaxe { #[allow(clippy::new_ret_no_self)] fn new(ip: IpAddr, model: impl MinerModel, version: Option) -> Box { - if version.is_some_and(|v| v >= Version::new(2, 0, 0) && v < Version::new(2, 9, 0)) { + if Bitaxe200::validate(version.as_ref()) { Box::new(Bitaxe200::new(ip, model)) + } else if Bitaxe290::validate(version.as_ref()) { + Box::new(Bitaxe290::new(ip, model)) } else { Box::new(Bitaxe290::new(ip, model)) } diff --git a/asic-rs-firmwares/bitaxe/src/backends/v2_0_0/mod.rs b/asic-rs-firmwares/bitaxe/src/backends/v2_0_0/mod.rs index 8f57e747..e600d96f 100644 --- a/asic-rs-firmwares/bitaxe/src/backends/v2_0_0/mod.rs +++ b/asic-rs-firmwares/bitaxe/src/backends/v2_0_0/mod.rs @@ -26,6 +26,7 @@ use asic_rs_makes_bitaxe::hardware::BitaxeControlBoard; use async_trait::async_trait; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature, Voltage}; +use semver::Version; use serde_json::Value; use web::BitaxeWebAPI; @@ -562,6 +563,14 @@ impl UpgradeFirmware for Bitaxe200 { impl HasAuth for Bitaxe200 {} impl HasDefaultAuth for Bitaxe200 {} +impl Validate for Bitaxe200 { + type Firmware = BitaxeFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v >= Version::new(2, 0, 0) && *v < Version::new(2, 9, 0)) + } +} + #[async_trait] impl SupportsTuningConfig for Bitaxe200 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/bitaxe/src/backends/v2_0_0/web.rs b/asic-rs-firmwares/bitaxe/src/backends/v2_0_0/web.rs index 750d6691..de531512 100644 --- a/asic-rs-firmwares/bitaxe/src/backends/v2_0_0/web.rs +++ b/asic-rs-firmwares/bitaxe/src/backends/v2_0_0/web.rs @@ -90,17 +90,17 @@ impl WebAPIClient for BitaxeWebAPI { Ok(json_data) => return Ok(json_data), Err(e) => { if attempt == self.retries { - return Err(BitaxeError::ParseError(e.to_string()))?; + Err(BitaxeError::ParseError(e.to_string()))?; } } } } else if attempt == self.retries { - return Err(BitaxeError::HttpError(response.status().as_u16()))?; + Err(BitaxeError::HttpError(response.status().as_u16()))?; } } Err(e) => { if attempt == self.retries { - return Err(e)?; + Err(e)?; } } } diff --git a/asic-rs-firmwares/bitaxe/src/backends/v2_9_0/mod.rs b/asic-rs-firmwares/bitaxe/src/backends/v2_9_0/mod.rs index 5d301719..ec23025b 100644 --- a/asic-rs-firmwares/bitaxe/src/backends/v2_9_0/mod.rs +++ b/asic-rs-firmwares/bitaxe/src/backends/v2_9_0/mod.rs @@ -26,6 +26,7 @@ use asic_rs_makes_bitaxe::hardware::BitaxeControlBoard; use async_trait::async_trait; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature, Voltage}; +use semver::Version; use serde_json::Value; use web::BitaxeWebAPI; @@ -546,6 +547,14 @@ impl UpgradeFirmware for Bitaxe290 { impl HasAuth for Bitaxe290 {} impl HasDefaultAuth for Bitaxe290 {} +impl Validate for Bitaxe290 { + type Firmware = BitaxeFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v >= Version::new(2, 9, 0)) + } +} + #[async_trait] impl SupportsTuningConfig for Bitaxe290 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/bitaxe/src/firmware.rs b/asic-rs-firmwares/bitaxe/src/firmware.rs index 143e0648..cf4558e9 100644 --- a/asic-rs-firmwares/bitaxe/src/firmware.rs +++ b/asic-rs-firmwares/bitaxe/src/firmware.rs @@ -35,7 +35,7 @@ impl DiscoveryCommands for BitaxeFirmware { #[async_trait] impl MinerFirmware for BitaxeFirmware { - async fn get_model(ip: IpAddr) -> Result { + async fn get_model(ip: IpAddr) -> Result { let response = util::send_web_command(&ip, "/api/system/info").await; match response { diff --git a/asic-rs-firmwares/braiins/src/backends/mod.rs b/asic-rs-firmwares/braiins/src/backends/mod.rs index ba720882..22766c42 100644 --- a/asic-rs-firmwares/braiins/src/backends/mod.rs +++ b/asic-rs-firmwares/braiins/src/backends/mod.rs @@ -8,11 +8,10 @@ pub mod v26_04; use std::net::IpAddr; use asic_rs_core::traits::{ - miner::{Miner, MinerConstructor}, + miner::{Miner, MinerConstructor, Validate}, model::MinerModel, }; -use semver::Version; use v21_09::BraiinsV2109; use v25_03::BraiinsV2503; use v25_05::BraiinsV2505; @@ -23,13 +22,18 @@ pub struct Braiins; impl MinerConstructor for Braiins { fn new(ip: IpAddr, model: impl MinerModel, version: Option) -> Box { - match version { - Some(ref v) if *v >= Version::new(26, 4, 0) => Box::new(BraiinsV2604::new(ip, model)), - Some(ref v) if *v >= Version::new(25, 7, 0) => Box::new(BraiinsV2507::new(ip, model)), - Some(ref v) if *v >= Version::new(25, 5, 0) => Box::new(BraiinsV2505::new(ip, model)), - Some(ref v) if *v >= Version::new(25, 3, 0) => Box::new(BraiinsV2503::new(ip, model)), - Some(ref v) if *v >= Version::new(24, 9, 0) => Box::new(BraiinsV2109::new(ip, model)), - _ => Box::new(BraiinsV2109::new(ip, model)), + if BraiinsV2604::validate(version.as_ref()) { + Box::new(BraiinsV2604::new(ip, model)) + } else if BraiinsV2507::validate(version.as_ref()) { + Box::new(BraiinsV2507::new(ip, model)) + } else if BraiinsV2505::validate(version.as_ref()) { + Box::new(BraiinsV2505::new(ip, model)) + } else if BraiinsV2503::validate(version.as_ref()) { + Box::new(BraiinsV2503::new(ip, model)) + } else if BraiinsV2109::validate(version.as_ref()) { + Box::new(BraiinsV2109::new(ip, model)) + } else { + Box::new(BraiinsV2109::new(ip, model)) } } } diff --git a/asic-rs-firmwares/braiins/src/backends/v21_09/mod.rs b/asic-rs-firmwares/braiins/src/backends/v21_09/mod.rs index c0326bde..056e65be 100644 --- a/asic-rs-firmwares/braiins/src/backends/v21_09/mod.rs +++ b/asic-rs-firmwares/braiins/src/backends/v21_09/mod.rs @@ -25,6 +25,7 @@ use asic_rs_core::{ use async_trait::async_trait; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature, Voltage}; +use semver::Version; use serde_json::{Value, json}; use web::BraiinsWebAPI; @@ -982,6 +983,14 @@ impl HasAuth for BraiinsV2109 { } } +impl Validate for BraiinsV2109 { + type Firmware = BraiinsFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v < Version::new(25, 3, 0)) + } +} + #[async_trait] impl SupportsTuningConfig for BraiinsV2109 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/braiins/src/backends/v25_03/mod.rs b/asic-rs-firmwares/braiins/src/backends/v25_03/mod.rs index 19abc5c8..e6e230d8 100644 --- a/asic-rs-firmwares/braiins/src/backends/v25_03/mod.rs +++ b/asic-rs-firmwares/braiins/src/backends/v25_03/mod.rs @@ -34,6 +34,7 @@ use asic_rs_core::{ use async_trait::async_trait; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature, Voltage}; +use semver::Version; use serde_json::{Value, json}; #[derive(Debug)] @@ -981,6 +982,14 @@ impl HasAuth for BraiinsV2503 { } } +impl Validate for BraiinsV2503 { + type Firmware = BraiinsFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v >= Version::new(25, 3, 0) && *v < Version::new(25, 5, 0)) + } +} + #[async_trait] impl SupportsTuningConfig for BraiinsV2503 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/braiins/src/backends/v25_05/mod.rs b/asic-rs-firmwares/braiins/src/backends/v25_05/mod.rs index 76a4d94d..0d15c482 100644 --- a/asic-rs-firmwares/braiins/src/backends/v25_05/mod.rs +++ b/asic-rs-firmwares/braiins/src/backends/v25_05/mod.rs @@ -25,6 +25,7 @@ use asic_rs_core::{ use async_trait::async_trait; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature, Voltage}; +use semver::Version; use serde_json::{Value, json}; use crate::{ @@ -986,6 +987,14 @@ impl HasAuth for BraiinsV2505 { } } +impl Validate for BraiinsV2505 { + type Firmware = BraiinsFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v >= Version::new(25, 5, 0) && *v < Version::new(25, 7, 0)) + } +} + #[async_trait] impl SupportsTuningConfig for BraiinsV2505 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/braiins/src/backends/v25_07/mod.rs b/asic-rs-firmwares/braiins/src/backends/v25_07/mod.rs index 72fd4cac..de6b5166 100644 --- a/asic-rs-firmwares/braiins/src/backends/v25_07/mod.rs +++ b/asic-rs-firmwares/braiins/src/backends/v25_07/mod.rs @@ -28,6 +28,7 @@ use async_trait::async_trait; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature, Voltage}; use reqwest::Method; +use semver::Version; use serde_json::{Value, json}; use web::BraiinsWebAPI; @@ -856,6 +857,14 @@ impl HasAuth for BraiinsV2507 { } } +impl Validate for BraiinsV2507 { + type Firmware = BraiinsFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v >= Version::new(25, 7, 0) && *v < Version::new(26, 4, 0)) + } +} + #[async_trait] impl SupportsTuningConfig for BraiinsV2507 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/braiins/src/backends/v26_04/mod.rs b/asic-rs-firmwares/braiins/src/backends/v26_04/mod.rs index 90d06df2..9653737e 100644 --- a/asic-rs-firmwares/braiins/src/backends/v26_04/mod.rs +++ b/asic-rs-firmwares/braiins/src/backends/v26_04/mod.rs @@ -28,6 +28,7 @@ use async_trait::async_trait; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature, Voltage}; use reqwest::Method; +use semver::Version; use serde_json::{Value, json}; use web::BraiinsWebAPI; @@ -873,6 +874,14 @@ impl HasAuth for BraiinsV2604 { } } +impl Validate for BraiinsV2604 { + type Firmware = BraiinsFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v >= Version::new(26, 4, 0)) + } +} + #[async_trait] impl SupportsTuningConfig for BraiinsV2604 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/braiins/src/firmware.rs b/asic-rs-firmwares/braiins/src/firmware.rs index 0632bb3e..05afa593 100644 --- a/asic-rs-firmwares/braiins/src/firmware.rs +++ b/asic-rs-firmwares/braiins/src/firmware.rs @@ -80,7 +80,7 @@ impl DiscoveryCommands for BraiinsFirmware { #[async_trait] impl MinerFirmware for BraiinsFirmware { - async fn get_model(ip: IpAddr) -> Result { + async fn get_model(ip: IpAddr) -> Result { if let Some(json_data) = util::send_graphql_command(&ip, "{ bosminer { info { modelName } } }").await && let Some(model_str) = json_data["data"]["bosminer"]["info"]["modelName"].as_str() diff --git a/asic-rs-firmwares/epic/src/backends/v1/mod.rs b/asic-rs-firmwares/epic/src/backends/v1/mod.rs index 6c59bc99..f6962a7d 100644 --- a/asic-rs-firmwares/epic/src/backends/v1/mod.rs +++ b/asic-rs-firmwares/epic/src/backends/v1/mod.rs @@ -1601,6 +1601,10 @@ impl HasAuth for PowerPlayV1 { } } +impl Validate for PowerPlayV1 { + type Firmware = EPicFirmware; +} + impl SupportsTemperatureConfig for PowerPlayV1 {} impl GetTuningPercent for PowerPlayV1 {} impl SetTuningPercent for PowerPlayV1 {} diff --git a/asic-rs-firmwares/epic/src/backends/v1/web.rs b/asic-rs-firmwares/epic/src/backends/v1/web.rs index 98705312..5fcf6299 100644 --- a/asic-rs-firmwares/epic/src/backends/v1/web.rs +++ b/asic-rs-firmwares/epic/src/backends/v1/web.rs @@ -188,7 +188,7 @@ impl PowerPlayWebAPI { let response = self.execute_request(&url, &Method::GET, None).await?; let status = response.status(); if !status.is_success() { - return Err(PowerPlayError::HttpError(status.as_u16()))?; + Err(PowerPlayError::HttpError(status.as_u16()))?; } response diff --git a/asic-rs-firmwares/epic/src/firmware.rs b/asic-rs-firmwares/epic/src/firmware.rs index 99ffabed..58fca2b4 100644 --- a/asic-rs-firmwares/epic/src/firmware.rs +++ b/asic-rs-firmwares/epic/src/firmware.rs @@ -105,7 +105,7 @@ impl DiscoveryCommands for EPicFirmware { #[async_trait] impl MinerFirmware for EPicFirmware { - async fn get_model(ip: IpAddr) -> Result { + async fn get_model(ip: IpAddr) -> Result { let url = format!("http://{}:4028/capabilities", ip); let response = build_discovery_client()? .get(&url) diff --git a/asic-rs-firmwares/futurebit/src/backends/v2/mod.rs b/asic-rs-firmwares/futurebit/src/backends/v2/mod.rs index 32258ec9..6538a74e 100644 --- a/asic-rs-firmwares/futurebit/src/backends/v2/mod.rs +++ b/asic-rs-firmwares/futurebit/src/backends/v2/mod.rs @@ -804,6 +804,10 @@ impl HasDefaultAuth for ApolloV2 { } } +impl Validate for ApolloV2 { + type Firmware = ApolloFirmware; +} + impl SupportsTemperatureConfig for ApolloV2 {} impl GetTuningPercent for ApolloV2 {} impl SetTuningPercent for ApolloV2 {} diff --git a/asic-rs-firmwares/futurebit/src/firmware.rs b/asic-rs-firmwares/futurebit/src/firmware.rs index 225098bd..c2cc89ec 100644 --- a/asic-rs-firmwares/futurebit/src/firmware.rs +++ b/asic-rs-firmwares/futurebit/src/firmware.rs @@ -69,7 +69,7 @@ async fn get_version_with_auth(ip: IpAddr, auth: &MinerAuth) -> Option Result { + async fn get_model(ip: IpAddr) -> Result { let auth = crate::backends::v2::ApolloV2::default_auth(); get_model_with_auth(ip, &auth).await } diff --git a/asic-rs-firmwares/luxminer/src/backends/v1/mod.rs b/asic-rs-firmwares/luxminer/src/backends/v1/mod.rs index c0df6d09..a82eb1eb 100644 --- a/asic-rs-firmwares/luxminer/src/backends/v1/mod.rs +++ b/asic-rs-firmwares/luxminer/src/backends/v1/mod.rs @@ -1183,6 +1183,10 @@ impl UpgradeFirmware for LuxMinerV1 { impl HasAuth for LuxMinerV1 {} impl HasDefaultAuth for LuxMinerV1 {} +impl Validate for LuxMinerV1 { + type Firmware = LuxMinerFirmware; +} + #[async_trait] impl SupportsTuningConfig for LuxMinerV1 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/luxminer/src/firmware.rs b/asic-rs-firmwares/luxminer/src/firmware.rs index 11db5e18..a66f4cc9 100644 --- a/asic-rs-firmwares/luxminer/src/firmware.rs +++ b/asic-rs-firmwares/luxminer/src/firmware.rs @@ -35,7 +35,7 @@ impl DiscoveryCommands for LuxMinerFirmware { #[async_trait] impl MinerFirmware for LuxMinerFirmware { - async fn get_model(ip: IpAddr) -> Result { + async fn get_model(ip: IpAddr) -> Result { let data = util::send_rpc_command(&ip, "version") .await .ok_or(ModelSelectionError::NoModelResponse)?; diff --git a/asic-rs-firmwares/marathon/src/backends/v1/mod.rs b/asic-rs-firmwares/marathon/src/backends/v1/mod.rs index 75e904f4..93076f22 100644 --- a/asic-rs-firmwares/marathon/src/backends/v1/mod.rs +++ b/asic-rs-firmwares/marathon/src/backends/v1/mod.rs @@ -1145,6 +1145,10 @@ impl HasAuth for MaraV1 { } } +impl Validate for MaraV1 { + type Firmware = MarathonFirmware; +} + #[async_trait] impl SupportsTuningConfig for MaraV1 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/marathon/src/firmware.rs b/asic-rs-firmwares/marathon/src/firmware.rs index 4c939da9..29aacc67 100644 --- a/asic-rs-firmwares/marathon/src/firmware.rs +++ b/asic-rs-firmwares/marathon/src/firmware.rs @@ -35,7 +35,7 @@ impl DiscoveryCommands for MarathonFirmware { #[async_trait] impl MinerFirmware for MarathonFirmware { - async fn get_model(ip: IpAddr) -> Result { + async fn get_model(ip: IpAddr) -> Result { let data = util::send_rpc_command(&ip, "version") .await .ok_or(ModelSelectionError::NoModelResponse)?; diff --git a/asic-rs-firmwares/nerdaxe/src/backends/v1/mod.rs b/asic-rs-firmwares/nerdaxe/src/backends/v1/mod.rs index 9e6456cd..becafdc3 100644 --- a/asic-rs-firmwares/nerdaxe/src/backends/v1/mod.rs +++ b/asic-rs-firmwares/nerdaxe/src/backends/v1/mod.rs @@ -548,6 +548,10 @@ impl UpgradeFirmware for NerdAxeV1 { impl HasAuth for NerdAxeV1 {} impl HasDefaultAuth for NerdAxeV1 {} +impl Validate for NerdAxeV1 { + type Firmware = NerdAxeFirmware; +} + #[async_trait] impl SupportsTuningConfig for NerdAxeV1 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/nerdaxe/src/backends/v1/web.rs b/asic-rs-firmwares/nerdaxe/src/backends/v1/web.rs index 09f761df..e0b2d1c9 100644 --- a/asic-rs-firmwares/nerdaxe/src/backends/v1/web.rs +++ b/asic-rs-firmwares/nerdaxe/src/backends/v1/web.rs @@ -56,17 +56,17 @@ impl WebAPIClient for NerdAxeWebAPI { Ok(json_data) => return Ok(json_data), Err(e) => { if attempt == self.retries { - return Err(NerdAxeError::ParseError(e.to_string()))?; + Err(NerdAxeError::ParseError(e.to_string()))?; } } } } else if attempt == self.retries { - return Err(NerdAxeError::HttpError(response.status().as_u16()))?; + Err(NerdAxeError::HttpError(response.status().as_u16()))?; } } Err(e) => { if attempt == self.retries { - return Err(e)?; + Err(e)?; } } } diff --git a/asic-rs-firmwares/nerdaxe/src/firmware.rs b/asic-rs-firmwares/nerdaxe/src/firmware.rs index 5dcbef5b..f2e319d1 100644 --- a/asic-rs-firmwares/nerdaxe/src/firmware.rs +++ b/asic-rs-firmwares/nerdaxe/src/firmware.rs @@ -35,7 +35,7 @@ impl DiscoveryCommands for NerdAxeFirmware { #[async_trait] impl MinerFirmware for NerdAxeFirmware { - async fn get_model(ip: IpAddr) -> Result { + async fn get_model(ip: IpAddr) -> Result { let (text, _, _) = util::send_web_command(&ip, "/api/system/info") .await .ok_or(ModelSelectionError::NoModelResponse)?; diff --git a/asic-rs-firmwares/proto/src/backends/v1/mod.rs b/asic-rs-firmwares/proto/src/backends/v1/mod.rs index e00a579e..d50013c0 100644 --- a/asic-rs-firmwares/proto/src/backends/v1/mod.rs +++ b/asic-rs-firmwares/proto/src/backends/v1/mod.rs @@ -1006,6 +1006,10 @@ impl HasAuth for ProtoV1 { } } +impl Validate for ProtoV1 { + type Firmware = ProtoFirmware; +} + impl SupportsTemperatureConfig for ProtoV1 {} impl GetTuningPercent for ProtoV1 {} impl SetTuningPercent for ProtoV1 {} diff --git a/asic-rs-firmwares/sealminer/src/backends/v2025/mod.rs b/asic-rs-firmwares/sealminer/src/backends/v2025/mod.rs index 0b773e9d..382906f6 100644 --- a/asic-rs-firmwares/sealminer/src/backends/v2025/mod.rs +++ b/asic-rs-firmwares/sealminer/src/backends/v2025/mod.rs @@ -837,6 +837,10 @@ impl HasDefaultAuth for SealMinerV2025 { } } +impl Validate for SealMinerV2025 { + type Firmware = SealMinerStockFirmware; +} + impl SupportsTemperatureConfig for SealMinerV2025 {} impl GetTuningPercent for SealMinerV2025 {} impl SetTuningPercent for SealMinerV2025 {} diff --git a/asic-rs-firmwares/sealminer/src/firmware.rs b/asic-rs-firmwares/sealminer/src/firmware.rs index b47c8aa1..c4d71bce 100644 --- a/asic-rs-firmwares/sealminer/src/firmware.rs +++ b/asic-rs-firmwares/sealminer/src/firmware.rs @@ -120,7 +120,7 @@ async fn get_version_with_auth(ip: IpAddr, auth: &MinerAuth) -> Option Result { + async fn get_model(ip: IpAddr) -> Result { let default = crate::backends::v2025::SealMinerV2025::default_auth(); get_model_with_auth(ip, &default).await } diff --git a/asic-rs-firmwares/vnish/src/backends/mod.rs b/asic-rs-firmwares/vnish/src/backends/mod.rs index 93267087..c7f38677 100644 --- a/asic-rs-firmwares/vnish/src/backends/mod.rs +++ b/asic-rs-firmwares/vnish/src/backends/mod.rs @@ -1,10 +1,9 @@ use std::net::IpAddr; use asic_rs_core::traits::{ - miner::{Miner, MinerConstructor}, + miner::{Miner, MinerConstructor, Validate}, model::MinerModel, }; -use semver::Version; pub use v1_2_0::VnishV120; pub use v1_3_0::VnishV130; @@ -16,11 +15,14 @@ pub struct Vnish; impl MinerConstructor for Vnish { #[allow(clippy::new_ret_no_self)] fn new(ip: IpAddr, model: impl MinerModel, version: Option) -> Box { - match version { - // Manual throttle (`tuning_percent`) was introduced in the 1.3.x line; - // assumed cutoff 1.3.0 (1.3.4 verified live, 1.2.x API has no endpoint). - Some(ref v) if *v >= Version::new(1, 3, 0) => Box::new(VnishV130::new(ip, model)), - _ => Box::new(VnishV120::new(ip, model)), + // Manual throttle (`tuning_percent`) was introduced in the 1.3.x line; + // assumed cutoff 1.3.0 (1.3.4 verified live, 1.2.x API has no endpoint). + if VnishV130::validate(version.as_ref()) { + Box::new(VnishV130::new(ip, model)) + } else if VnishV120::validate(version.as_ref()) { + Box::new(VnishV120::new(ip, model)) + } else { + Box::new(VnishV120::new(ip, model)) } } } diff --git a/asic-rs-firmwares/vnish/src/backends/v1_2_0/mod.rs b/asic-rs-firmwares/vnish/src/backends/v1_2_0/mod.rs index 79706d4b..a7edc0e7 100644 --- a/asic-rs-firmwares/vnish/src/backends/v1_2_0/mod.rs +++ b/asic-rs-firmwares/vnish/src/backends/v1_2_0/mod.rs @@ -25,6 +25,7 @@ use asic_rs_makes_antminer::hardware::AntMinerControlBoard; use async_trait::async_trait; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature, Voltage}; +use semver::Version; use serde_json::{Value, json}; use web::VnishWebAPI; @@ -1015,6 +1016,14 @@ impl HasAuth for VnishV120 { } } +impl Validate for VnishV120 { + type Firmware = VnishFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v < Version::new(1, 3, 0)) + } +} + #[async_trait] impl SupportsTuningConfig for VnishV120 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/vnish/src/backends/v1_2_0/web.rs b/asic-rs-firmwares/vnish/src/backends/v1_2_0/web.rs index cedfafce..1b197692 100644 --- a/asic-rs-firmwares/vnish/src/backends/v1_2_0/web.rs +++ b/asic-rs-firmwares/vnish/src/backends/v1_2_0/web.rs @@ -283,7 +283,7 @@ impl VnishWebAPI { let response = self.execute_request(&url, &Method::GET, None).await?; let status = response.status(); if !status.is_success() { - return Err(VnishError::HttpError(status.as_u16()))?; + Err(VnishError::HttpError(status.as_u16()))?; } response diff --git a/asic-rs-firmwares/vnish/src/backends/v1_3_0/mod.rs b/asic-rs-firmwares/vnish/src/backends/v1_3_0/mod.rs index ad1f37ec..7581f022 100644 --- a/asic-rs-firmwares/vnish/src/backends/v1_3_0/mod.rs +++ b/asic-rs-firmwares/vnish/src/backends/v1_3_0/mod.rs @@ -25,6 +25,7 @@ use asic_rs_makes_antminer::hardware::AntMinerControlBoard; use async_trait::async_trait; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature, Voltage}; +use semver::Version; use serde_json::{Value, json}; use web::VnishWebAPI; @@ -1038,6 +1039,14 @@ impl HasAuth for VnishV130 { } } +impl Validate for VnishV130 { + type Firmware = VnishFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v >= Version::new(1, 3, 0)) + } +} + #[async_trait] impl SupportsTuningConfig for VnishV130 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/vnish/src/backends/v1_3_0/web.rs b/asic-rs-firmwares/vnish/src/backends/v1_3_0/web.rs index 06ee3290..b37bafb5 100644 --- a/asic-rs-firmwares/vnish/src/backends/v1_3_0/web.rs +++ b/asic-rs-firmwares/vnish/src/backends/v1_3_0/web.rs @@ -294,7 +294,7 @@ impl VnishWebAPI { let response = self.execute_request(&url, &Method::GET, None).await?; let status = response.status(); if !status.is_success() { - return Err(VnishError::HttpError(status.as_u16()))?; + Err(VnishError::HttpError(status.as_u16()))?; } response diff --git a/asic-rs-firmwares/vnish/src/firmware.rs b/asic-rs-firmwares/vnish/src/firmware.rs index 20c3da62..26199d21 100644 --- a/asic-rs-firmwares/vnish/src/firmware.rs +++ b/asic-rs-firmwares/vnish/src/firmware.rs @@ -35,7 +35,7 @@ impl DiscoveryCommands for VnishFirmware { #[async_trait] impl MinerFirmware for VnishFirmware { - async fn get_model(ip: IpAddr) -> Result { + async fn get_model(ip: IpAddr) -> Result { let (text, _, _) = util::send_web_command(&ip, "/api/v1/info") .await .ok_or(ModelSelectionError::NoModelResponse)?; diff --git a/asic-rs-firmwares/volcminer/src/backends/v1/mod.rs b/asic-rs-firmwares/volcminer/src/backends/v1/mod.rs index 260e09d5..f18ea55e 100644 --- a/asic-rs-firmwares/volcminer/src/backends/v1/mod.rs +++ b/asic-rs-firmwares/volcminer/src/backends/v1/mod.rs @@ -806,6 +806,10 @@ impl HasAuth for VolcMinerV1 { } } +impl Validate for VolcMinerV1 { + type Firmware = VolcMinerStockFirmware; +} + #[async_trait] impl SupportsTuningConfig for VolcMinerV1 {} diff --git a/asic-rs-firmwares/volcminer/src/firmware.rs b/asic-rs-firmwares/volcminer/src/firmware.rs index 60c1cddf..8afdaf36 100644 --- a/asic-rs-firmwares/volcminer/src/firmware.rs +++ b/asic-rs-firmwares/volcminer/src/firmware.rs @@ -80,7 +80,7 @@ async fn get_version_with_auth(ip: IpAddr, auth: &MinerAuth) -> Option Result { + async fn get_model(ip: IpAddr) -> Result { let default = VolcMinerV1::default_auth(); get_model_with_auth(ip, &default).await } diff --git a/asic-rs-firmwares/whatsminer/src/backends/mod.rs b/asic-rs-firmwares/whatsminer/src/backends/mod.rs index 8d86b406..f8bdac10 100644 --- a/asic-rs-firmwares/whatsminer/src/backends/mod.rs +++ b/asic-rs-firmwares/whatsminer/src/backends/mod.rs @@ -1,10 +1,9 @@ use std::net::IpAddr; use asic_rs_core::traits::{ - miner::{Miner, MinerConstructor}, + miner::{Miner, MinerConstructor, Validate}, model::MinerModel, }; -use semver::Version; pub use v1::WhatsMinerV1; pub use v2::WhatsMinerV2; pub use v3::WhatsMinerV3; @@ -18,14 +17,12 @@ pub struct WhatsMiner; impl MinerConstructor for WhatsMiner { #[allow(clippy::new_ret_no_self)] fn new(ip: IpAddr, model: impl MinerModel, version: Option) -> Box { - if let Some(v) = version { - if v >= Version::new(2024, 11, 0) { - Box::new(WhatsMinerV3::new(ip, model)) - } else if v >= Version::new(2022, 7, 29) { - Box::new(WhatsMinerV2::new(ip, model)) - } else { - Box::new(WhatsMinerV1::new(ip, model)) - } + if WhatsMinerV3::validate(version.as_ref()) { + Box::new(WhatsMinerV3::new(ip, model)) + } else if WhatsMinerV2::validate(version.as_ref()) { + Box::new(WhatsMinerV2::new(ip, model)) + } else if WhatsMinerV1::validate(version.as_ref()) { + Box::new(WhatsMinerV1::new(ip, model)) } else { Box::new(WhatsMinerV1::new(ip, model)) } diff --git a/asic-rs-firmwares/whatsminer/src/backends/v1/mod.rs b/asic-rs-firmwares/whatsminer/src/backends/v1/mod.rs index 4cb1903b..db7998cd 100644 --- a/asic-rs-firmwares/whatsminer/src/backends/v1/mod.rs +++ b/asic-rs-firmwares/whatsminer/src/backends/v1/mod.rs @@ -26,6 +26,7 @@ use async_trait::async_trait; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature}; use rpc::WhatsMinerRPCAPI; +use semver::Version; use serde_json::Value; use crate::firmware::WhatsMinerFirmware; @@ -621,6 +622,14 @@ impl UpgradeFirmware for WhatsMinerV1 { impl HasAuth for WhatsMinerV1 {} impl HasDefaultAuth for WhatsMinerV1 {} +impl Validate for WhatsMinerV1 { + type Firmware = WhatsMinerFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v < Version::new(2022, 7, 29)) + } +} + #[async_trait] impl SupportsTuningConfig for WhatsMinerV1 { fn supports_tuning_config(&self) -> bool { diff --git a/asic-rs-firmwares/whatsminer/src/backends/v2/mod.rs b/asic-rs-firmwares/whatsminer/src/backends/v2/mod.rs index b8cb4e12..caa089b7 100644 --- a/asic-rs-firmwares/whatsminer/src/backends/v2/mod.rs +++ b/asic-rs-firmwares/whatsminer/src/backends/v2/mod.rs @@ -30,6 +30,7 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature}; use rpc::WhatsMinerRPCAPI; +use semver::Version; use serde_json::{Value, json}; use crate::firmware::WhatsMinerFirmware; @@ -773,6 +774,14 @@ impl HasAuth for WhatsMinerV2 { } } +impl Validate for WhatsMinerV2 { + type Firmware = WhatsMinerFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v >= Version::new(2022, 7, 29) && *v < Version::new(2024, 11, 0)) + } +} + /// Maps a TuningConfig to the WhatsMiner V2 RPC command name and parameter. fn tuning_config_to_rpc(config: &TuningConfig) -> anyhow::Result<(&'static str, Option)> { match &config.target { diff --git a/asic-rs-firmwares/whatsminer/src/backends/v3/mod.rs b/asic-rs-firmwares/whatsminer/src/backends/v3/mod.rs index a0326a57..d6e38e93 100644 --- a/asic-rs-firmwares/whatsminer/src/backends/v3/mod.rs +++ b/asic-rs-firmwares/whatsminer/src/backends/v3/mod.rs @@ -31,6 +31,7 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use macaddr::MacAddr; use measurements::{AngularVelocity, Frequency, Power, Temperature}; pub(crate) use rpc::WhatsMinerRPCAPI; +use semver::Version; use serde_json::{Value, json}; use crate::backends::v2::rpc::WhatsMinerRPCAPI as WhatsMinerV2RPC; @@ -806,6 +807,14 @@ impl HasAuth for WhatsMinerV3 { } } +impl Validate for WhatsMinerV3 { + type Firmware = WhatsMinerFirmware; + + fn validate(version: Option<&semver::Version>) -> bool { + version.is_some_and(|v| *v >= Version::new(2024, 11, 0)) + } +} + /// Maps a TuningConfig to the WhatsMiner V3 RPC command name and parameter. fn tuning_config_to_v3_rpc(config: &TuningConfig) -> anyhow::Result<(&'static str, Value)> { match &config.target { diff --git a/asic-rs-firmwares/whatsminer/src/firmware.rs b/asic-rs-firmwares/whatsminer/src/firmware.rs index 7e792732..e0609e5d 100644 --- a/asic-rs-firmwares/whatsminer/src/firmware.rs +++ b/asic-rs-firmwares/whatsminer/src/firmware.rs @@ -59,7 +59,7 @@ impl DiscoveryCommands for WhatsMinerFirmware { #[async_trait] impl MinerFirmware for WhatsMinerFirmware { - async fn get_model(ip: IpAddr) -> Result { + async fn get_model(ip: IpAddr) -> Result { let use_v3 = if let Some(data) = util::send_rpc_command(&ip, "get_version").await { data["Msg"]["fw_ver"] .as_str() diff --git a/docs/api.md b/docs/api.md index a671ef9e..614d03ae 100644 --- a/docs/api.md +++ b/docs/api.md @@ -103,6 +103,10 @@ Python exposes matching `supports_*` properties. | `supports_factory_reset` | `factory_reset()` | | `supports_upgrade_firmware` | `upgrade_firmware(...)` | +All miner handles also expose `revalidate()`, which re-runs that backend's +firmware discovery checks against the same IP and returns whether the device is +still valid for the existing miner handle. + === "Rust" ```rust diff --git a/python/pyasic_rs/asic_rs.pyi b/python/pyasic_rs/asic_rs.pyi index 27ab09e7..260e154c 100644 --- a/python/pyasic_rs/asic_rs.pyi +++ b/python/pyasic_rs/asic_rs.pyi @@ -402,6 +402,7 @@ class Miner: def read_logs(self, /) -> Awaitable[str |None]: ... def restart(self, /) -> Awaitable[bool |None]: ... def resume(self, /, at_time: "timedelta | float | int | None" = None) -> Awaitable[bool |None]: ... + def revalidate(self, /) -> Awaitable[bool |None]: ... def set_auth(self, /, username: str, password: str) -> None: ... def set_fan_config(self, /, config: "FanConfig") -> Awaitable[bool |None]: ... def set_fault_light(self, /, fault: bool) -> Awaitable[bool |None]: ... diff --git a/src/python/miner.rs b/src/python/miner.rs index c08d484a..00e414c7 100644 --- a/src/python/miner.rs +++ b/src/python/miner.rs @@ -608,6 +608,18 @@ impl Miner { Ok(data.ok()) }) } + /// Re-run this miner backend's discovery checks against the same IP. + /// + /// Returns `False` when the device is offline, no longer responds as the + /// same firmware/model, or would no longer be valid for this backend. + pub fn revalidate<'a>(&self, py: Python<'a>) -> PyResult>> { + let inner = Arc::clone(&self.inner); + future_into_py(py, async move { + let inner = inner.read().await; + let data = inner.revalidate().await; + Ok(data.ok()) + }) + } /// Factory reset the miner. /// /// Returns `None` if factory reset is unsupported or rejected by the backend.