asic_rs/miners/backends/btminer/v1/
mod.rs

1pub use rpc::BTMinerRPCAPI;
2
3use std::collections::HashMap;
4use std::net::IpAddr;
5use std::str::FromStr;
6use std::time::Duration;
7
8use macaddr::MacAddr;
9use measurements::{AngularVelocity, Frequency, Power, Temperature};
10use serde_json::Value;
11
12use crate::data::board::BoardData;
13use crate::data::device::MinerMake;
14use crate::data::device::{DeviceInfo, HashAlgorithm, MinerFirmware, MinerModel};
15use crate::data::fan::FanData;
16use crate::data::hashrate::{HashRate, HashRateUnit};
17use crate::data::message::{MessageSeverity, MinerMessage};
18use crate::data::pool::{PoolData, PoolURL};
19use crate::miners::backends::traits::*;
20use crate::miners::commands::MinerCommand;
21use crate::miners::data::{
22    DataCollector, DataExtensions, DataExtractor, DataField, DataLocation, get_by_pointer,
23};
24
25mod rpc;
26
27#[derive(Debug)]
28pub struct BTMiner1 {
29    pub ip: IpAddr,
30    pub rpc: BTMinerRPCAPI,
31    pub device_info: DeviceInfo,
32}
33
34impl BTMiner1 {
35    pub fn new(ip: IpAddr, model: MinerModel, firmware: MinerFirmware) -> Self {
36        BTMiner1 {
37            ip,
38            rpc: BTMinerRPCAPI::new(ip, None),
39            device_info: DeviceInfo::new(
40                MinerMake::WhatsMiner,
41                model,
42                firmware,
43                HashAlgorithm::SHA256,
44            ),
45        }
46    }
47}
48
49impl GetDataLocations for BTMiner1 {
50    fn get_locations(&self, data_field: DataField) -> Vec<DataLocation> {
51        let summary_cmd: MinerCommand = MinerCommand::RPC {
52            command: "summary",
53            parameters: None,
54        };
55        let devs_cmd: MinerCommand = MinerCommand::RPC {
56            command: "devs",
57            parameters: None,
58        };
59        let pools_cmd: MinerCommand = MinerCommand::RPC {
60            command: "pools",
61            parameters: None,
62        };
63        let status_cmd: MinerCommand = MinerCommand::RPC {
64            command: "status",
65            parameters: None,
66        };
67        let get_version_cmd: MinerCommand = MinerCommand::RPC {
68            command: "get_version",
69            parameters: None,
70        };
71        let get_psu_cmd: MinerCommand = MinerCommand::RPC {
72            command: "get_psu",
73            parameters: None,
74        };
75
76        match data_field {
77            DataField::Mac => vec![(
78                summary_cmd,
79                DataExtractor {
80                    func: get_by_pointer,
81                    key: Some("/SUMMARY/0/MAC"),
82                    tag: None,
83                },
84            )],
85            DataField::ApiVersion => vec![(
86                get_version_cmd,
87                DataExtractor {
88                    func: get_by_pointer,
89                    key: Some("/Msg/api_ver"),
90                    tag: None,
91                },
92            )],
93            DataField::FirmwareVersion => vec![(
94                get_version_cmd,
95                DataExtractor {
96                    func: get_by_pointer,
97                    key: Some("/Msg/fw_ver"),
98                    tag: None,
99                },
100            )],
101            DataField::ControlBoardVersion => vec![(
102                get_version_cmd,
103                DataExtractor {
104                    func: get_by_pointer,
105                    key: Some("/Msg/platform"),
106                    tag: None,
107                },
108            )],
109            DataField::WattageLimit => vec![(
110                summary_cmd,
111                DataExtractor {
112                    func: get_by_pointer,
113                    key: Some("/SUMMARY/0/Power Limit"),
114                    tag: None,
115                },
116            )],
117            DataField::Fans => vec![(
118                summary_cmd,
119                DataExtractor {
120                    func: get_by_pointer,
121                    key: Some("/SUMMARY/0"),
122                    tag: None,
123                },
124            )],
125            DataField::PsuFans => vec![(
126                get_psu_cmd,
127                DataExtractor {
128                    func: get_by_pointer,
129                    key: Some("/Msg/fan_speed"),
130                    tag: None,
131                },
132            )],
133            DataField::Hashboards => vec![(
134                devs_cmd,
135                DataExtractor {
136                    func: get_by_pointer,
137                    key: Some(""),
138                    tag: None,
139                },
140            )],
141            DataField::Pools => vec![(
142                pools_cmd,
143                DataExtractor {
144                    func: get_by_pointer,
145                    key: Some("/POOLS"),
146                    tag: None,
147                },
148            )],
149            DataField::Uptime => vec![(
150                summary_cmd,
151                DataExtractor {
152                    func: get_by_pointer,
153                    key: Some("/SUMMARY/0/Elapsed"),
154                    tag: None,
155                },
156            )],
157            DataField::Wattage => vec![(
158                summary_cmd,
159                DataExtractor {
160                    func: get_by_pointer,
161                    key: Some("/SUMMARY/0/Power"),
162                    tag: None,
163                },
164            )],
165            DataField::Hashrate => vec![(
166                summary_cmd,
167                DataExtractor {
168                    func: get_by_pointer,
169                    key: Some("/SUMMARY/0/HS RT"),
170                    tag: None,
171                },
172            )],
173            DataField::ExpectedHashrate => vec![(
174                summary_cmd,
175                DataExtractor {
176                    func: get_by_pointer,
177                    key: Some("/SUMMARY/0/Factory GHS"),
178                    tag: None,
179                },
180            )],
181            DataField::FluidTemperature => vec![(
182                summary_cmd,
183                DataExtractor {
184                    func: get_by_pointer,
185                    key: Some("/SUMMARY/0/Env Temp"),
186                    tag: None,
187                },
188            )],
189            DataField::IsMining => vec![(
190                status_cmd,
191                DataExtractor {
192                    func: get_by_pointer,
193                    key: Some("/SUMMARY/0/btmineroff"),
194                    tag: None,
195                },
196            )],
197            DataField::Messages => vec![(
198                summary_cmd,
199                DataExtractor {
200                    func: get_by_pointer,
201                    key: Some("/SUMMARY/0"),
202                    tag: None,
203                },
204            )],
205            _ => vec![],
206        }
207    }
208}
209
210impl GetIP for BTMiner1 {
211    fn get_ip(&self) -> IpAddr {
212        self.ip
213    }
214}
215impl GetDeviceInfo for BTMiner1 {
216    fn get_device_info(&self) -> DeviceInfo {
217        self.device_info
218    }
219}
220
221impl CollectData for BTMiner1 {
222    fn get_collector(&self) -> DataCollector<'_> {
223        DataCollector::new(self, &self.rpc)
224    }
225}
226
227impl GetMAC for BTMiner1 {
228    fn parse_mac(&self, data: &HashMap<DataField, Value>) -> Option<MacAddr> {
229        data.extract::<String>(DataField::Mac)
230            .and_then(|s| MacAddr::from_str(&s).ok())
231    }
232}
233
234impl GetSerialNumber for BTMiner1 {}
235impl GetHostname for BTMiner1 {}
236impl GetApiVersion for BTMiner1 {
237    fn parse_api_version(&self, data: &HashMap<DataField, Value>) -> Option<String> {
238        data.extract::<String>(DataField::ApiVersion)
239    }
240}
241impl GetFirmwareVersion for BTMiner1 {
242    fn parse_firmware_version(&self, data: &HashMap<DataField, Value>) -> Option<String> {
243        data.extract::<String>(DataField::FirmwareVersion)
244    }
245}
246impl GetControlBoardVersion for BTMiner1 {
247    fn parse_control_board_version(&self, data: &HashMap<DataField, Value>) -> Option<String> {
248        data.extract::<String>(DataField::ControlBoardVersion)
249    }
250}
251impl GetHashboards for BTMiner1 {
252    fn parse_hashboards(&self, data: &HashMap<DataField, Value>) -> Vec<BoardData> {
253        let mut hashboards: Vec<BoardData> = Vec::new();
254        let board_count = self.device_info.hardware.boards.unwrap_or(3);
255        let hashboard_data = data.get(&DataField::Hashboards);
256
257        for idx in 0..board_count {
258            let hashrate = hashboard_data
259                .and_then(|val| val.pointer(&format!("/DEVS/{}/MHS av", idx)))
260                .and_then(|val| val.as_f64())
261                .map(|f| {
262                    HashRate {
263                        value: f,
264                        unit: HashRateUnit::MegaHash,
265                        algo: String::from("SHA256"),
266                    }
267                    .as_unit(HashRateUnit::TeraHash)
268                });
269            let expected_hashrate = hashboard_data
270                .and_then(|val| val.pointer(&format!("/DEVS/{}/Factory GHS", idx)))
271                .and_then(|val| val.as_f64())
272                .map(|f| {
273                    HashRate {
274                        value: f,
275                        unit: HashRateUnit::GigaHash,
276                        algo: String::from("SHA256"),
277                    }
278                    .as_unit(HashRateUnit::TeraHash)
279                });
280            let board_temperature = hashboard_data
281                .and_then(|val| val.pointer(&format!("/DEVS/{}/Temperature", idx)))
282                .and_then(|val| val.as_f64())
283                .map(Temperature::from_celsius);
284            let intake_temperature = hashboard_data
285                .and_then(|val| val.pointer(&format!("/DEVS/{}/Chip Temp Min", idx)))
286                .and_then(|val| val.as_f64())
287                .map(Temperature::from_celsius);
288            let outlet_temperature = hashboard_data
289                .and_then(|val| val.pointer(&format!("/DEVS/{}/Chip Temp Max", idx)))
290                .and_then(|val| val.as_f64())
291                .map(Temperature::from_celsius);
292            let serial_number = hashboard_data
293                .and_then(|val| val.pointer(&format!("/DEVS/{}/PCB SN", idx)))
294                .and_then(|val| val.as_str())
295                .map(String::from);
296            let working_chips = hashboard_data
297                .and_then(|val| val.pointer(&format!("/DEVS/{}/Effective Chips", idx)))
298                .and_then(|val| val.as_u64())
299                .map(|u| u as u16);
300            let frequency = hashboard_data
301                .and_then(|val| val.pointer(&format!("/DEVS/{}/Frequency", idx)))
302                .and_then(|val| val.as_f64())
303                .map(Frequency::from_megahertz);
304
305            let active = Some(hashrate.clone().map(|h| h.value).unwrap_or(0f64) > 0f64);
306            hashboards.push(BoardData {
307                hashrate,
308                position: idx,
309                expected_hashrate,
310                board_temperature,
311                intake_temperature,
312                outlet_temperature,
313                expected_chips: self.device_info.hardware.chips,
314                working_chips,
315                serial_number,
316                chips: vec![],
317                voltage: None, // TODO
318                frequency,
319                tuned: Some(true),
320                active,
321            });
322        }
323        hashboards
324    }
325}
326impl GetHashrate for BTMiner1 {
327    fn parse_hashrate(&self, data: &HashMap<DataField, Value>) -> Option<HashRate> {
328        data.extract_map::<f64, _>(DataField::Hashrate, |f| {
329            HashRate {
330                value: f,
331                unit: HashRateUnit::MegaHash,
332                algo: String::from("SHA256"),
333            }
334            .as_unit(HashRateUnit::TeraHash)
335        })
336    }
337}
338impl GetExpectedHashrate for BTMiner1 {
339    fn parse_expected_hashrate(&self, data: &HashMap<DataField, Value>) -> Option<HashRate> {
340        data.extract_map::<f64, _>(DataField::ExpectedHashrate, |f| {
341            HashRate {
342                value: f,
343                unit: HashRateUnit::GigaHash,
344                algo: String::from("SHA256"),
345            }
346            .as_unit(HashRateUnit::TeraHash)
347        })
348    }
349}
350impl GetFans for BTMiner1 {
351    fn parse_fans(&self, data: &HashMap<DataField, Value>) -> Vec<FanData> {
352        let mut fans: Vec<FanData> = Vec::new();
353        for (idx, direction) in ["In", "Out"].iter().enumerate() {
354            let fan = data.extract_nested_map::<f64, _>(
355                DataField::Fans,
356                &format!("Fan Speed {}", direction),
357                |rpm| FanData {
358                    position: idx as i16,
359                    rpm: Some(AngularVelocity::from_rpm(rpm)),
360                },
361            );
362            if let Some(f) = fan {
363                fans.push(f)
364            }
365        }
366        fans
367    }
368}
369impl GetPsuFans for BTMiner1 {
370    fn parse_psu_fans(&self, data: &HashMap<DataField, Value>) -> Vec<FanData> {
371        let mut psu_fans: Vec<FanData> = Vec::new();
372
373        let psu_fan = data.extract_map::<String, _>(DataField::PsuFans, |rpm| FanData {
374            position: 0i16,
375            rpm: Some(AngularVelocity::from_rpm(rpm.parse().unwrap())),
376        });
377        if let Some(f) = psu_fan {
378            psu_fans.push(f)
379        }
380        psu_fans
381    }
382}
383impl GetFluidTemperature for BTMiner1 {
384    fn parse_fluid_temperature(&self, data: &HashMap<DataField, Value>) -> Option<Temperature> {
385        data.extract_map::<f64, _>(DataField::FluidTemperature, Temperature::from_celsius)
386    }
387}
388impl GetWattage for BTMiner1 {
389    fn parse_wattage(&self, data: &HashMap<DataField, Value>) -> Option<Power> {
390        data.extract_map::<f64, _>(DataField::Wattage, Power::from_watts)
391    }
392}
393impl GetWattageLimit for BTMiner1 {
394    fn parse_wattage_limit(&self, data: &HashMap<DataField, Value>) -> Option<Power> {
395        data.extract_map::<f64, _>(DataField::WattageLimit, Power::from_watts)
396    }
397}
398impl GetLightFlashing for BTMiner1 {}
399impl GetMessages for BTMiner1 {
400    fn parse_messages(&self, data: &HashMap<DataField, Value>) -> Vec<MinerMessage> {
401        let mut messages = Vec::new();
402
403        let error_count = data
404            .get(&DataField::Messages)
405            .and_then(|val| {
406                val.pointer("/Error Code Count")
407                    .and_then(|val| val.as_u64())
408            })
409            .unwrap_or(0u64) as usize;
410        for idx in 0..error_count {
411            let e_code = data
412                .get(&DataField::Messages)
413                .and_then(|val| val.pointer(&format!("/Error Code {}", idx)))
414                .and_then(|val| val.as_u64());
415            if let Some(code) = e_code {
416                messages.push(MinerMessage::new(
417                    0,
418                    code,
419                    "".to_string(), // TODO: parse message from mapping
420                    MessageSeverity::Error,
421                ));
422            }
423        }
424
425        messages
426    }
427}
428impl GetUptime for BTMiner1 {
429    fn parse_uptime(&self, data: &HashMap<DataField, Value>) -> Option<Duration> {
430        data.extract_map::<u64, _>(DataField::Uptime, Duration::from_secs)
431    }
432}
433impl GetIsMining for BTMiner1 {
434    fn parse_is_mining(&self, data: &HashMap<DataField, Value>) -> bool {
435        data.extract_map::<String, _>(DataField::IsMining, |l| l != "false")
436            .unwrap_or(true)
437    }
438}
439impl GetPools for BTMiner1 {
440    fn parse_pools(&self, data: &HashMap<DataField, Value>) -> Vec<PoolData> {
441        let mut pools: Vec<PoolData> = Vec::new();
442        let pools_raw = data.get(&DataField::Pools);
443        if pools_raw.is_some() {
444            let pools_response = pools_raw.unwrap();
445            for (idx, _) in pools_response
446                .as_array()
447                .unwrap_or(&Vec::new())
448                .iter()
449                .enumerate()
450            {
451                let user = pools_raw
452                    .and_then(|val| val.pointer(&format!("/{}/User", idx)))
453                    .map(|val| String::from(val.as_str().unwrap_or("")));
454
455                let alive = pools_raw
456                    .and_then(|val| val.pointer(&format!("/{}/Status", idx)))
457                    .map(|val| val.as_str())
458                    .map(|val| val == Some("Alive"));
459
460                let active = pools_raw
461                    .and_then(|val| val.pointer(&format!("/{}/Stratum Active", idx)))
462                    .and_then(|val| val.as_bool());
463
464                let url = pools_raw
465                    .and_then(|val| val.pointer(&format!("/{}/URL", idx)))
466                    .map(|val| PoolURL::from(String::from(val.as_str().unwrap_or(""))));
467
468                let accepted_shares = pools_raw
469                    .and_then(|val| val.pointer(&format!("/{}/Accepted", idx)))
470                    .and_then(|val| val.as_u64());
471
472                let rejected_shares = pools_raw
473                    .and_then(|val| val.pointer(&format!("/{}/Rejected", idx)))
474                    .and_then(|val| val.as_u64());
475
476                pools.push(PoolData {
477                    position: Some(idx as u16),
478                    url,
479                    accepted_shares,
480                    rejected_shares,
481                    active,
482                    alive,
483                    user,
484                });
485            }
486        }
487        pools
488    }
489}