1use crate::miners::{
2 backends::traits::{APIClient, MinerInterface},
3 commands::MinerCommand,
4};
5use serde_json::{Value, json};
6use std::collections::{HashMap, HashSet};
7use strum::{EnumIter, IntoEnumIterator};
8
9#[derive(Debug, Clone, Hash, Eq, PartialEq, Copy, EnumIter)]
11pub enum DataField {
12 SchemaVersion,
14 Timestamp,
16 Ip,
18 Mac,
20 DeviceInfo,
22 SerialNumber,
24 Hostname,
26 ApiVersion,
28 FirmwareVersion,
30 ControlBoardVersion,
32 Hashboards,
34 Hashrate,
36 ExpectedHashrate,
38 Fans,
40 PsuFans,
42 AverageTemperature,
44 FluidTemperature,
46 Wattage,
48 WattageLimit,
50 Efficiency,
52 LightFlashing,
54 Messages,
56 Uptime,
58 IsMining,
60 Pools,
62}
63
64type ExtractorFn = for<'a> fn(&'a Value, Option<&'static str>) -> Option<&'a Value>;
67
68#[derive(Clone, Copy)]
72pub struct DataExtractor {
73 pub func: ExtractorFn,
75 pub key: Option<&'static str>,
77 pub tag: Option<&'static str>,
79}
80
81pub type DataLocation = (MinerCommand, DataExtractor);
83
84pub fn get_by_key<'a>(data: &'a Value, key: Option<&str>) -> Option<&'a Value> {
88 data.get(key?.to_string())
89}
90
91pub fn get_by_pointer<'a>(data: &'a Value, pointer: Option<&str>) -> Option<&'a Value> {
95 data.pointer(pointer?)
96}
97
98pub trait FromValue: Sized {
100 fn from_value(value: &Value) -> Option<Self>;
102}
103
104impl FromValue for String {
106 fn from_value(value: &Value) -> Option<Self> {
107 value.as_str().map(String::from)
108 }
109}
110
111impl FromValue for f64 {
112 fn from_value(value: &Value) -> Option<Self> {
113 value.as_f64()
114 }
115}
116
117impl FromValue for u64 {
118 fn from_value(value: &Value) -> Option<Self> {
119 value.as_u64()
120 }
121}
122
123impl FromValue for i64 {
124 fn from_value(value: &Value) -> Option<Self> {
125 value.as_i64()
126 }
127}
128
129impl FromValue for bool {
130 fn from_value(value: &Value) -> Option<Self> {
131 value.as_bool().or_else(|| {
133 value
135 .as_u64()
136 .map(|n| n != 0)
137 .or_else(|| value.as_i64().map(|n| n != 0))
138 })
139 }
140}
141
142impl<T: FromValue> FromValue for Vec<T> {
143 fn from_value(value: &Value) -> Option<Self> {
144 value.as_array()?.iter().map(|v| T::from_value(v)).collect()
145 }
146}
147
148pub trait DataExtensions {
150 fn extract<T: FromValue>(&self, field: DataField) -> Option<T>;
152
153 fn extract_or<T: FromValue>(&self, field: DataField, default: T) -> T;
155
156 fn extract_nested<T: FromValue>(&self, field: DataField, nested_key: &str) -> Option<T>;
158
159 fn extract_nested_or<T: FromValue>(&self, field: DataField, nested_key: &str, default: T) -> T;
161
162 fn extract_map<T: FromValue, U>(&self, field: DataField, f: impl FnOnce(T) -> U) -> Option<U>;
164
165 fn extract_map_or<T: FromValue, U>(
167 &self,
168 field: DataField,
169 default: U,
170 f: impl FnOnce(T) -> U,
171 ) -> U;
172
173 fn extract_nested_map<T: FromValue, U>(
175 &self,
176 field: DataField,
177 nested_key: &str,
178 f: impl FnOnce(T) -> U,
179 ) -> Option<U>;
180
181 fn extract_nested_map_or<T: FromValue, U>(
183 &self,
184 field: DataField,
185 nested_key: &str,
186 default: U,
187 f: impl FnOnce(T) -> U,
188 ) -> U;
189}
190
191impl DataExtensions for HashMap<DataField, Value> {
192 fn extract<T: FromValue>(&self, field: DataField) -> Option<T> {
193 self.get(&field).and_then(|v| T::from_value(v))
194 }
195
196 fn extract_or<T: FromValue>(&self, field: DataField, default: T) -> T {
197 self.extract(field).unwrap_or(default)
198 }
199
200 fn extract_nested<T: FromValue>(&self, field: DataField, nested_key: &str) -> Option<T> {
201 self.get(&field)
202 .and_then(|v| v.get(nested_key))
203 .and_then(|v| T::from_value(v))
204 }
205
206 fn extract_nested_or<T: FromValue>(&self, field: DataField, nested_key: &str, default: T) -> T {
207 self.extract_nested(field, nested_key).unwrap_or(default)
208 }
209
210 fn extract_map<T: FromValue, U>(&self, field: DataField, f: impl FnOnce(T) -> U) -> Option<U> {
211 self.extract(field).map(f)
212 }
213
214 fn extract_map_or<T: FromValue, U>(
215 &self,
216 field: DataField,
217 default: U,
218 f: impl FnOnce(T) -> U,
219 ) -> U {
220 self.extract(field).map(f).unwrap_or(default)
221 }
222
223 fn extract_nested_map<T: FromValue, U>(
224 &self,
225 field: DataField,
226 nested_key: &str,
227 f: impl FnOnce(T) -> U,
228 ) -> Option<U> {
229 self.extract_nested(field, nested_key).map(f)
230 }
231
232 fn extract_nested_map_or<T: FromValue, U>(
233 &self,
234 field: DataField,
235 nested_key: &str,
236 default: U,
237 f: impl FnOnce(T) -> U,
238 ) -> U {
239 self.extract_nested(field, nested_key)
240 .map(f)
241 .unwrap_or(default)
242 }
243}
244
245pub struct DataCollector<'a> {
247 miner: &'a dyn MinerInterface,
249 client: &'a dyn APIClient,
250 cache: HashMap<MinerCommand, Value>,
252}
253
254impl<'a> DataCollector<'a> {
255 pub fn new(miner: &'a dyn MinerInterface) -> Self {
257 Self {
258 miner,
259 client: miner,
260 cache: HashMap::new(),
261 }
262 }
263
264 #[allow(dead_code)]
265 pub(crate) fn new_with_client(
266 miner: &'a dyn MinerInterface,
267 client: &'a dyn APIClient,
268 ) -> Self {
269 Self {
270 miner,
271 client,
272 cache: HashMap::new(),
273 }
274 }
275
276 pub async fn collect_all(&mut self) -> HashMap<DataField, Value> {
278 self.collect(DataField::iter().collect::<Vec<_>>().as_slice())
279 .await
280 }
281
282 pub async fn collect(&mut self, fields: &[DataField]) -> HashMap<DataField, Value> {
286 let mut results = HashMap::new();
287 let required_commands = self.get_required_commands(fields);
288
289 for command in required_commands {
290 if let Ok(response) = self.client.get_api_result(&command).await {
291 self.cache.insert(command, response);
292 }
293 }
294
295 for &field in fields {
297 if let Some(value) = self.extract_field(field) {
298 results.insert(field, value);
299 }
300 }
301
302 results
303 }
304
305 fn merge(&self, a: &mut Value, b: Value) {
306 Self::merge_values(a, b);
307 }
308
309 fn merge_values(a: &mut Value, b: Value) {
310 match (a, b) {
311 (Value::Object(a_map), Value::Object(b_map)) => {
312 for (k, v) in b_map {
313 Self::merge_values(a_map.entry(k).or_insert(Value::Null), v);
314 }
315 }
316 (Value::Array(a_array), Value::Array(b_array)) => {
317 a_array.extend(b_array);
319 }
320 (a_slot, b_val) => {
321 *a_slot = b_val;
323 }
324 }
325 }
326
327 fn get_required_commands(&self, fields: &[DataField]) -> HashSet<MinerCommand> {
331 fields
332 .iter()
333 .flat_map(|&field| self.miner.get_locations(field))
334 .map(|(cmd, _)| cmd.clone())
335 .collect()
336 }
337
338 fn extract_field(&self, field: DataField) -> Option<Value> {
342 let mut success: Vec<Value> = Vec::new();
343 for (command, extractor) in self.miner.get_locations(field) {
344 if let Some(response_data) = self.cache.get(&command)
345 && let Some(value) = (extractor.func)(response_data, extractor.key)
346 {
347 match extractor.tag {
348 Some(tag) => {
349 let tag = tag.to_string();
350 success.push(json!({ tag: value.clone() }).clone());
351 }
352 None => {
353 success.push(value.clone());
354 }
355 }
356 }
357 }
358 if success.is_empty() {
359 None
360 } else {
361 let mut response = json!({});
362 for value in success {
363 self.merge(&mut response, value)
364 }
365 Some(response)
366 }
367 }
368}