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#[async_trait]
34pub trait GetMinerData: CollectData {
35 async fn get_data(&self) -> MinerData;
38 fn parse_data(&self, data: HashMap<DataField, Value>) -> MinerData;
39}
40
41pub trait CollectData: GetDataLocations {
42 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 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 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 schema_version,
151 timestamp,
152
153 ip,
155 mac,
156
157 device_info,
159 serial_number,
160 hostname,
161
162 api_version,
164 firmware_version,
165 control_board_version,
166
167 expected_hashboards: device_info.hardware.boards,
169 hashboards,
170 hashrate,
171 expected_hashrate,
172
173 expected_chips: device_info.hardware.chips,
175 total_chips,
176
177 expected_fans: device_info.hardware.fans,
179 fans,
180 psu_fans,
181 average_temperature,
182 fluid_temperature,
183
184 wattage,
186 wattage_limit,
187 efficiency,
188
189 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
226pub trait GetIP: Send + Sync {
228 fn get_ip(&self) -> IpAddr;
230}
231
232pub trait GetDeviceInfo: Send + Sync {
233 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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}