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