asic_rs/miners/backends/
traits.rs

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