asic_rs/miners/backends/epic/
web.rs

1use crate::miners::{
2    api::{APIClient, WebAPIClient},
3    commands::MinerCommand,
4};
5use anyhow::{Result, anyhow};
6use async_trait::async_trait;
7use reqwest::{Client, Method, Response};
8use serde_json::{Value, json};
9use std::{net::IpAddr, time::Duration};
10
11/// ePIC PowerPlay WebAPI client
12
13#[derive(Debug)]
14pub struct PowerPlayWebAPI {
15    client: Client,
16    pub ip: IpAddr,
17    port: u16,
18    timeout: Duration,
19    password: Option<String>,
20}
21
22#[async_trait]
23impl APIClient for PowerPlayWebAPI {
24    async fn get_api_result(&self, command: &MinerCommand) -> Result<Value> {
25        match command {
26            MinerCommand::WebAPI {
27                command,
28                parameters,
29            } => self
30                .send_command(command, false, parameters.clone(), Method::GET)
31                .await
32                .map_err(|e| anyhow!(e.to_string())),
33            _ => Err(anyhow!("Cannot send non web command to web API")),
34        }
35    }
36}
37
38#[async_trait]
39impl WebAPIClient for PowerPlayWebAPI {
40    /// Send a command to the EPic miner API
41    async fn send_command(
42        &self,
43        command: &str,
44        _privileged: bool,
45        parameters: Option<Value>,
46        method: Method,
47    ) -> Result<Value> {
48        let url = format!("http://{}:{}/{}", self.ip, self.port, command);
49
50        let response = self
51            .execute_request(&url, &method, parameters.clone())
52            .await?;
53
54        let status = response.status();
55        if status.is_success() {
56            let json_data = response
57                .json()
58                .await
59                .map_err(|e| PowerPlayError::ParseError(e.to_string()))?;
60            Ok(json_data)
61        } else {
62            Err(PowerPlayError::HttpError(status.as_u16()))?
63        }
64    }
65}
66
67impl PowerPlayWebAPI {
68    /// Create a new EPic WebAPI client
69    pub fn new(ip: IpAddr, port: u16) -> Self {
70        let client = Client::builder()
71            .timeout(Duration::from_secs(10))
72            .build()
73            .expect("Failed to create HTTP client");
74
75        Self {
76            client,
77            ip,
78            port,
79            timeout: Duration::from_secs(5),
80            password: Some("letmein".to_string()), // Default password
81        }
82    }
83
84    /// Execute the actual HTTP request
85    async fn execute_request(
86        &self,
87        url: &str,
88        method: &Method,
89        parameters: Option<Value>,
90    ) -> Result<Response, PowerPlayError> {
91        let request_builder = match *method {
92            Method::GET => self.client.get(url),
93            Method::POST => self.client.post(url).json(&{
94                let mut p = parameters.unwrap_or_else(|| json!({}));
95                p.as_object_mut().map(|m| {
96                    m.insert(
97                        "password".into(),
98                        Value::String(self.password.clone().unwrap_or_else(|| "letmein".into())),
99                    )
100                });
101                p
102            }),
103            _ => return Err(PowerPlayError::UnsupportedMethod(method.to_string())),
104        };
105
106        let request_builder = request_builder.timeout(self.timeout);
107
108        let request = request_builder
109            .build()
110            .map_err(|e| PowerPlayError::RequestError(e.to_string()))?;
111
112        let response = self
113            .client
114            .execute(request)
115            .await
116            .map_err(|e| PowerPlayError::NetworkError(e.to_string()))?;
117
118        Ok(response)
119    }
120}
121
122/// Error types for EPic WebAPI operations
123#[derive(Debug, Clone)]
124pub enum PowerPlayError {
125    /// Network error (connection issues, DNS resolution, etc.)
126    NetworkError(String),
127    /// HTTP error with status code
128    HttpError(u16),
129    /// JSON parsing error
130    ParseError(String),
131    /// Request building error
132    RequestError(String),
133    /// Timeout error
134    Timeout,
135    /// Unsupported HTTP method
136    UnsupportedMethod(String),
137    /// Maximum retries exceeded
138    MaxRetriesExceeded,
139    /// Authentication failed
140    AuthenticationFailed,
141    /// Unauthorized (401)
142    Unauthorized,
143}
144
145impl std::fmt::Display for PowerPlayError {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        match self {
148            PowerPlayError::NetworkError(msg) => write!(f, "Network error: {msg}"),
149            PowerPlayError::HttpError(code) => write!(f, "HTTP error: {code}"),
150            PowerPlayError::ParseError(msg) => write!(f, "Parse error: {msg}"),
151            PowerPlayError::RequestError(msg) => write!(f, "Request error: {msg}"),
152            PowerPlayError::Timeout => write!(f, "Request timeout"),
153            PowerPlayError::UnsupportedMethod(method) => write!(f, "Unsupported method: {method}"),
154            PowerPlayError::MaxRetriesExceeded => write!(f, "Maximum retries exceeded"),
155            PowerPlayError::AuthenticationFailed => write!(f, "Authentication failed"),
156            PowerPlayError::Unauthorized => write!(f, "Unauthorized access"),
157        }
158    }
159}
160
161impl std::error::Error for PowerPlayError {}