asic_rs/miners/backends/
traits.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use macaddr::MacAddr;
4use measurements::{Power, Temperature};
5use reqwest::Method;
6use serde_json::Value;
7use std::collections::HashMap;
8use std::fmt::Debug;
9use std::net::IpAddr;
10use std::time::{Duration, SystemTime, UNIX_EPOCH};
11
12use crate::data::board::BoardData;
13use crate::data::device::{DeviceInfo, MinerModel};
14use crate::data::fan::FanData;
15use crate::data::hashrate::{HashRate, HashRateUnit};
16use crate::data::message::MinerMessage;
17use crate::data::pool::PoolData;
18use crate::miners::commands::MinerCommand;
19
20use crate::data::miner::MinerData;
21use crate::miners::data::{DataCollector, DataField, DataLocation};
22
23pub(crate) trait MinerConstructor {
24    #[allow(clippy::new_ret_no_self)]
25    fn new(
26        ip: IpAddr,
27        model: MinerModel,
28        version: Option<semver::Version>,
29    ) -> Box<dyn GetMinerData>;
30}
31
32/// Trait that every miner backend must implement to provide miner data.
33#[async_trait]
34pub trait GetMinerData: CollectData {
35    /// Asynchronously retrieves standardized information about a miner,
36    /// returning it as a `MinerData` struct.
37    async fn get_data(&self) -> MinerData;
38    fn parse_data(&self, data: HashMap<DataField, Value>) -> MinerData;
39}
40
41pub trait CollectData: GetDataLocations {
42    /// Returns a `DataCollector` that can be used to collect data from the miner.
43    ///
44    /// This method is responsible for creating and returning a `DataCollector`
45    /// instance that can be used to collect data from the miner.
46    fn get_collector(&self) -> DataCollector<'_>;
47}
48
49pub trait MinerInterface: GetDataLocations + APIClient {}
50
51impl<T: GetDataLocations + APIClient> MinerInterface for T {}
52
53pub trait GetDataLocations: Send + Sync + Debug {
54    /// Returns the locations of the specified data field on the miner.
55    ///
56    /// This associates API commands (routes) with `DataExtractor` structs,
57    /// describing how to extract the data for a given `DataField`.
58    fn get_locations(&self, data_field: DataField) -> Vec<DataLocation>;
59}
60
61#[async_trait]
62impl<
63    T: GetIP
64        + GetDeviceInfo
65        + GetExpectedHashboards
66        + GetExpectedChips
67        + GetExpectedFans
68        + GetMAC
69        + GetSerialNumber
70        + GetHostname
71        + GetApiVersion
72        + GetFirmwareVersion
73        + GetControlBoardVersion
74        + GetHashboards
75        + GetHashrate
76        + GetExpectedHashrate
77        + GetFans
78        + GetPsuFans
79        + GetFluidTemperature
80        + GetWattage
81        + GetWattageLimit
82        + GetLightFlashing
83        + GetMessages
84        + GetUptime
85        + GetIsMining
86        + GetPools,
87> GetMinerData for T
88{
89    async fn get_data(&self) -> MinerData {
90        let mut collector = self.get_collector();
91        let data = collector.collect_all().await;
92        self.parse_data(data)
93    }
94    fn parse_data(&self, data: HashMap<DataField, Value>) -> MinerData {
95        let schema_version = env!("CARGO_PKG_VERSION").to_string();
96        let timestamp = SystemTime::now()
97            .duration_since(UNIX_EPOCH)
98            .expect("Failed to get system time")
99            .as_secs();
100
101        let ip = self.get_ip();
102        let mac = self.parse_mac(&data);
103        let serial_number = self.parse_serial_number(&data);
104        let hostname = self.parse_hostname(&data);
105        let api_version = self.parse_api_version(&data);
106        let firmware_version = self.parse_firmware_version(&data);
107        let control_board_version = self.parse_control_board_version(&data);
108        let uptime = self.parse_uptime(&data);
109        let hashrate = self.parse_hashrate(&data);
110        let expected_hashrate = self.parse_expected_hashrate(&data);
111        let wattage = self.parse_wattage(&data);
112        let wattage_limit = self.parse_wattage_limit(&data);
113        let fluid_temperature = self.parse_fluid_temperature(&data);
114        let fans = self.parse_fans(&data);
115        let psu_fans = self.parse_psu_fans(&data);
116        let hashboards = self.parse_hashboards(&data);
117        let light_flashing = self.parse_light_flashing(&data);
118        let is_mining = self.parse_is_mining(&data);
119        let messages = self.parse_messages(&data);
120        let pools = self.parse_pools(&data);
121        let device_info = self.get_device_info();
122
123        // computed fields
124        let total_chips = hashboards.clone().iter().map(|b| b.working_chips).sum();
125        let average_temperature = {
126            let board_temps = hashboards
127                .iter()
128                .map(|b| b.board_temperature)
129                .filter(|x| x.is_some())
130                .map(|x| x.unwrap().as_celsius())
131                .collect::<Vec<f64>>();
132            if !board_temps.is_empty() {
133                Some(Temperature::from_celsius(
134                    board_temps.iter().sum::<f64>() / hashboards.len() as f64,
135                ))
136            } else {
137                None
138            }
139        };
140        let efficiency = match (hashrate.as_ref(), wattage.as_ref()) {
141            (Some(hr), Some(w)) => {
142                let hashrate_th = hr.clone().as_unit(HashRateUnit::TeraHash).value;
143                Some(w.as_watts() / hashrate_th)
144            }
145            _ => None,
146        };
147
148        MinerData {
149            // Version information
150            schema_version,
151            timestamp,
152
153            // Network identification
154            ip,
155            mac,
156
157            // Device identification
158            device_info,
159            serial_number,
160            hostname,
161
162            // Version information
163            api_version,
164            firmware_version,
165            control_board_version,
166
167            // Hashboard information
168            expected_hashboards: device_info.hardware.boards,
169            hashboards,
170            hashrate,
171            expected_hashrate,
172
173            // Chip information
174            expected_chips: device_info.hardware.chips,
175            total_chips,
176
177            // Cooling information
178            expected_fans: device_info.hardware.fans,
179            fans,
180            psu_fans,
181            average_temperature,
182            fluid_temperature,
183
184            // Power information
185            wattage,
186            wattage_limit,
187            efficiency,
188
189            // Status information
190            light_flashing,
191            messages,
192            uptime,
193            is_mining,
194
195            pools,
196        }
197    }
198}
199
200#[async_trait]
201pub trait APIClient: Send + Sync {
202    async fn get_api_result(&self, command: &MinerCommand) -> Result<Value>;
203}
204
205#[async_trait]
206pub trait WebAPIClient: APIClient {
207    async fn send_command(
208        &self,
209        command: &str,
210        _privileged: bool,
211        parameters: Option<Value>,
212        method: Method,
213    ) -> Result<Value>;
214}
215
216#[async_trait]
217pub trait RPCAPIClient: APIClient {
218    async fn send_command(
219        &self,
220        command: &str,
221        _privileged: bool,
222        parameters: Option<Value>,
223    ) -> Result<Value>;
224}
225
226// Data traits
227pub trait GetIP: Send + Sync {
228    /// Returns the IP address of the miner.
229    fn get_ip(&self) -> IpAddr;
230}
231
232pub trait GetDeviceInfo: Send + Sync {
233    /// Returns information about the miner.
234    fn get_device_info(&self) -> DeviceInfo;
235}
236
237trait GetExpectedHashboards: GetDeviceInfo {
238    #[allow(dead_code)]
239    fn get_expected_hashboards(&self) -> Option<u8> {
240        self.get_device_info().hardware.boards
241    }
242}
243impl<T: GetDeviceInfo> GetExpectedHashboards for T {}
244
245trait GetExpectedChips: GetDeviceInfo {
246    #[allow(dead_code)]
247    fn get_expected_chips(&self) -> Option<u16> {
248        self.get_device_info().hardware.chips
249    }
250}
251impl<T: GetDeviceInfo> GetExpectedChips for T {}
252
253trait GetExpectedFans: GetDeviceInfo {
254    #[allow(dead_code)]
255    fn get_expected_fans(&self) -> Option<u8> {
256        self.get_device_info().hardware.fans
257    }
258}
259impl<T: GetDeviceInfo> GetExpectedFans for T {}
260
261// MAC Address
262#[async_trait]
263pub trait GetMAC: CollectData {
264    async fn get_mac(&self) -> Option<MacAddr> {
265        let mut collector = self.get_collector();
266        let data = collector.collect(&[DataField::Mac]).await;
267        self.parse_mac(&data)
268    }
269    #[allow(unused_variables)]
270    fn parse_mac(&self, data: &HashMap<DataField, Value>) -> Option<MacAddr> {
271        None
272    }
273}
274
275// Serial Number
276#[async_trait]
277pub trait GetSerialNumber: CollectData {
278    async fn get_serial_number(&self) -> Option<String> {
279        let mut collector = self.get_collector();
280        let data = collector.collect(&[DataField::SerialNumber]).await;
281        self.parse_serial_number(&data)
282    }
283    #[allow(unused_variables)]
284    fn parse_serial_number(&self, data: &HashMap<DataField, Value>) -> Option<String> {
285        None
286    }
287}
288
289// Hostname
290#[async_trait]
291pub trait GetHostname: CollectData {
292    async fn get_hostname(&self) -> Option<String> {
293        let mut collector = self.get_collector();
294        let data = collector.collect(&[DataField::Hostname]).await;
295        self.parse_hostname(&data)
296    }
297    #[allow(unused_variables)]
298    fn parse_hostname(&self, data: &HashMap<DataField, Value>) -> Option<String> {
299        None
300    }
301}
302
303// API Version
304#[async_trait]
305pub trait GetApiVersion: CollectData {
306    async fn get_api_version(&self) -> Option<String> {
307        let mut collector = self.get_collector();
308        let data = collector.collect(&[DataField::ApiVersion]).await;
309        self.parse_api_version(&data)
310    }
311    #[allow(unused_variables)]
312    fn parse_api_version(&self, data: &HashMap<DataField, Value>) -> Option<String> {
313        None
314    }
315}
316
317// Firmware Version
318#[async_trait]
319pub trait GetFirmwareVersion: CollectData {
320    async fn get_firmware_version(&self) -> Option<String> {
321        let mut collector = self.get_collector();
322        let data = collector.collect(&[DataField::FirmwareVersion]).await;
323        self.parse_firmware_version(&data)
324    }
325    #[allow(unused_variables)]
326    fn parse_firmware_version(&self, data: &HashMap<DataField, Value>) -> Option<String> {
327        None
328    }
329}
330
331// Control Board Version
332#[async_trait]
333pub trait GetControlBoardVersion: CollectData {
334    async fn get_control_board_version(&self) -> Option<String> {
335        let mut collector = self.get_collector();
336        let data = collector.collect(&[DataField::ControlBoardVersion]).await;
337        self.parse_control_board_version(&data)
338    }
339    #[allow(unused_variables)]
340    fn parse_control_board_version(&self, data: &HashMap<DataField, Value>) -> Option<String> {
341        None
342    }
343}
344// Hashboards
345#[async_trait]
346pub trait GetHashboards: CollectData {
347    async fn get_hashboards(&self) -> Vec<BoardData> {
348        let mut collector = self.get_collector();
349        let data = collector.collect(&[DataField::Hashboards]).await;
350        self.parse_hashboards(&data)
351    }
352    #[allow(unused_variables)]
353    fn parse_hashboards(&self, data: &HashMap<DataField, Value>) -> Vec<BoardData> {
354        vec![]
355    }
356}
357
358// Hashrate
359#[async_trait]
360pub trait GetHashrate: CollectData {
361    async fn get_hashrate(&self) -> Option<HashRate> {
362        let mut collector = self.get_collector();
363        let data = collector.collect(&[DataField::Hashrate]).await;
364        self.parse_hashrate(&data)
365    }
366    #[allow(unused_variables)]
367    fn parse_hashrate(&self, data: &HashMap<DataField, Value>) -> Option<HashRate> {
368        None
369    }
370}
371
372// Expected Hashrate
373#[async_trait]
374pub trait GetExpectedHashrate: CollectData {
375    async fn get_expected_hashrate(&self) -> Option<HashRate> {
376        let mut collector = self.get_collector();
377        let data = collector.collect(&[DataField::ExpectedHashrate]).await;
378        self.parse_expected_hashrate(&data)
379    }
380    #[allow(unused_variables)]
381    fn parse_expected_hashrate(&self, data: &HashMap<DataField, Value>) -> Option<HashRate> {
382        None
383    }
384}
385
386// Fans
387#[async_trait]
388pub trait GetFans: CollectData {
389    async fn get_fans(&self) -> Vec<FanData> {
390        let mut collector = self.get_collector();
391        let data = collector.collect(&[DataField::Fans]).await;
392        self.parse_fans(&data)
393    }
394    #[allow(unused_variables)]
395    fn parse_fans(&self, data: &HashMap<DataField, Value>) -> Vec<FanData> {
396        vec![]
397    }
398}
399
400// PSU Fans
401#[async_trait]
402pub trait GetPsuFans: CollectData {
403    async fn get_psu_fans(&self) -> Vec<FanData> {
404        let mut collector = self.get_collector();
405        let data = collector.collect(&[DataField::PsuFans]).await;
406        self.parse_psu_fans(&data)
407    }
408    #[allow(unused_variables)]
409    fn parse_psu_fans(&self, data: &HashMap<DataField, Value>) -> Vec<FanData> {
410        vec![]
411    }
412}
413
414// Fluid Temperature
415#[async_trait]
416pub trait GetFluidTemperature: CollectData {
417    async fn get_fluid_temperature(&self) -> Option<Temperature> {
418        let mut collector = self.get_collector();
419        let data = collector.collect(&[DataField::FluidTemperature]).await;
420        self.parse_fluid_temperature(&data)
421    }
422    #[allow(unused_variables)]
423    fn parse_fluid_temperature(&self, data: &HashMap<DataField, Value>) -> Option<Temperature> {
424        None
425    }
426}
427
428// Wattage
429#[async_trait]
430pub trait GetWattage: CollectData {
431    async fn get_wattage(&self) -> Option<Power> {
432        let mut collector = self.get_collector();
433        let data = collector.collect(&[DataField::Wattage]).await;
434        self.parse_wattage(&data)
435    }
436    #[allow(unused_variables)]
437    fn parse_wattage(&self, data: &HashMap<DataField, Value>) -> Option<Power> {
438        None
439    }
440}
441
442// Wattage Limit
443#[async_trait]
444pub trait GetWattageLimit: CollectData {
445    async fn get_wattage_limit(&self) -> Option<Power> {
446        let mut collector = self.get_collector();
447        let data = collector.collect(&[DataField::WattageLimit]).await;
448        self.parse_wattage_limit(&data)
449    }
450    #[allow(unused_variables)]
451    fn parse_wattage_limit(&self, data: &HashMap<DataField, Value>) -> Option<Power> {
452        None
453    }
454}
455
456// Light Flashing
457#[async_trait]
458pub trait GetLightFlashing: CollectData {
459    async fn get_light_flashing(&self) -> Option<bool> {
460        let mut collector = self.get_collector();
461        let data = collector.collect(&[DataField::LightFlashing]).await;
462        self.parse_light_flashing(&data)
463    }
464    #[allow(unused_variables)]
465    fn parse_light_flashing(&self, data: &HashMap<DataField, Value>) -> Option<bool> {
466        None
467    }
468}
469
470// Setters
471#[async_trait]
472pub trait SetFaultLight {
473    async fn set_fault_light(&self, fault: bool) -> Result<bool>;
474}
475#[async_trait]
476pub trait SetPowerLimit {
477    async fn set_power_limit(&self, limit: Power) -> Result<bool>;
478}
479
480#[async_trait]
481pub trait Restart {
482    async fn restart(&self) -> Result<bool>;
483}
484
485#[async_trait]
486pub trait Pause {
487    async fn pause(&self, at_time: Option<Duration>) -> Result<bool>;
488}
489
490#[async_trait]
491pub trait Resume {
492    async fn resume(&self, at_time: Option<Duration>) -> Result<bool>;
493}
494
495// Messages
496#[async_trait]
497pub trait GetMessages: CollectData {
498    async fn get_messages(&self) -> Vec<MinerMessage> {
499        let mut collector = self.get_collector();
500        let data = collector.collect(&[DataField::Messages]).await;
501        self.parse_messages(&data)
502    }
503    #[allow(unused_variables)]
504    fn parse_messages(&self, data: &HashMap<DataField, Value>) -> Vec<MinerMessage> {
505        vec![]
506    }
507}
508
509// Uptime
510#[async_trait]
511pub trait GetUptime: CollectData {
512    async fn get_uptime(&self) -> Option<Duration> {
513        let mut collector = self.get_collector();
514        let data = collector.collect(&[DataField::Uptime]).await;
515        self.parse_uptime(&data)
516    }
517    #[allow(unused_variables)]
518    fn parse_uptime(&self, data: &HashMap<DataField, Value>) -> Option<Duration> {
519        None
520    }
521}
522
523// Is Mining
524#[async_trait]
525pub trait GetIsMining: CollectData {
526    async fn get_is_mining(&self) -> bool {
527        let mut collector = self.get_collector();
528        let data = collector.collect(&[DataField::IsMining]).await;
529        self.parse_is_mining(&data)
530    }
531    #[allow(unused_variables)]
532    fn parse_is_mining(&self, data: &HashMap<DataField, Value>) -> bool {
533        true
534    }
535}
536
537// Pools
538#[async_trait]
539pub trait GetPools: CollectData {
540    async fn get_pools(&self) -> Vec<PoolData> {
541        let mut collector = self.get_collector();
542        let data = collector.collect(&[DataField::Pools]).await;
543        self.parse_pools(&data)
544    }
545    #[allow(unused_variables)]
546    fn parse_pools(&self, data: &HashMap<DataField, Value>) -> Vec<PoolData> {
547        vec![]
548    }
549}