use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use asic_rs::{
core::data::{collector::DataField, hashrate::HashRateUnit, miner::MinerData},
MinerFactory,
};
use std::net::IpAddr;
use tracing::debug;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct MinerTelemetry {
pub make: Option<String>,
pub model: Option<String>,
pub firmware_version: Option<String>,
pub reported_hashrate_hs: Option<f64>,
pub power_consumption_w: Option<f64>,
pub efficiency_j_per_th: Option<f64>,
pub average_temperature_c: Option<f64>,
pub uptime_secs: Option<u64>,
pub is_mining: Option<bool>,
}
impl From<MinerData> for MinerTelemetry {
fn from(data: MinerData) -> Self {
Self {
make: Some(data.device_info.make),
model: Some(data.device_info.model),
firmware_version: data.firmware_version,
reported_hashrate_hs: data
.hashrate
.map(|hashrate| hashrate.as_unit(HashRateUnit::Hash).value),
power_consumption_w: data.wattage.map(|power| power.as_watts()),
efficiency_j_per_th: data.efficiency,
average_temperature_c: data
.average_temperature
.map(|temperature| temperature.as_celsius()),
uptime_secs: data.uptime.map(|uptime| uptime.as_secs()),
is_mining: Some(data.is_mining),
}
}
}
const MINER_TELEMETRY_EXCLUDED_FIELDS: &[DataField] = &[
DataField::Mac,
DataField::SerialNumber,
DataField::Hostname,
DataField::ApiVersion,
DataField::ControlBoardVersion,
DataField::Chips,
DataField::ExpectedHashrate,
DataField::Fans,
DataField::PsuFans,
DataField::FluidTemperature,
DataField::TuningTarget,
DataField::LightFlashing,
DataField::Messages,
DataField::Pools,
];
pub struct MinerTelemetryCollector {
factory: MinerFactory,
}
impl MinerTelemetryCollector {
pub fn new() -> Self {
Self {
factory: MinerFactory::new(),
}
}
pub async fn fetch(&self, ip: IpAddr) -> Option<MinerTelemetry> {
let miner = match self.factory.get_miner(ip).await {
Ok(Some(miner)) => miner,
Ok(None) => {
debug!("No miner management interface found at {ip}");
return None;
}
Err(error) => {
debug!("Failed to get miner management interface at {ip}: {error}");
return None;
}
};
Some(MinerTelemetry::from(
miner
.get_data_filtered(MINER_TELEMETRY_EXCLUDED_FIELDS.to_vec())
.await,
))
}
}
impl Default for MinerTelemetryCollector {
fn default() -> Self {
Self::new()
}
}