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#[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 async fn get_data(&self) -> MinerData;
69 fn parse_data(&self, data: HashMap<DataField, Value>) -> MinerData;
70}
71
72pub trait CollectData: GetDataLocations {
73 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 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 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 schema_version,
183 timestamp,
184
185 ip,
187 mac,
188
189 device_info,
191 serial_number,
192 hostname,
193
194 api_version,
196 firmware_version,
197 control_board_version,
198
199 expected_hashboards: device_info.hardware.boards,
201 hashboards,
202 hashrate,
203 expected_hashrate,
204
205 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 expected_fans: device_info.hardware.fans,
214 fans,
215 psu_fans,
216 average_temperature,
217 fluid_temperature,
218
219 wattage,
221 wattage_limit,
222 efficiency,
223
224 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
261pub trait GetIP: Send + Sync {
263 fn get_ip(&self) -> IpAddr;
265}
266
267pub trait GetDeviceInfo: Send + Sync {
268 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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}