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, 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(), 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}