asic_rs/miners/backends/espminer/v2_0_0/
web.rs1use 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;
9use std::{net::IpAddr, time::Duration};
10use tokio::time::timeout;
11
12#[derive(Debug)]
14pub struct ESPMinerWebAPI {
15 client: Client,
16 pub ip: IpAddr,
17 port: u16,
18 timeout: Duration,
19 retries: u32,
20}
21
22#[async_trait]
23#[allow(dead_code)]
24trait ESPMiner200WebAPI: WebAPIClient {
25 async fn system_info(&self) -> Result<Value> {
27 self.send_command("system/info", false, None, Method::GET)
28 .await
29 }
30
31 async fn swarm_info(&self) -> Result<Value> {
33 self.send_command("swarm/info", false, None, Method::GET)
34 .await
35 }
36
37 async fn restart(&self) -> Result<Value> {
39 self.send_command("system/restart", false, None, Method::POST)
40 .await
41 }
42
43 async fn update_settings(&self, config: Value) -> Result<Value> {
45 self.send_command("system", false, Some(config), Method::PATCH)
46 .await
47 }
48}
49
50#[async_trait]
51impl APIClient for ESPMinerWebAPI {
52 async fn get_api_result(&self, command: &MinerCommand) -> Result<Value> {
53 match command {
54 MinerCommand::WebAPI {
55 command,
56 parameters,
57 } => self
58 .send_command(command, false, parameters.clone(), Method::GET)
59 .await
60 .map_err(|e| anyhow!(e.to_string())),
61 _ => Err(anyhow!("Cannot send non web command to web API")),
62 }
63 }
64}
65
66#[async_trait]
67impl WebAPIClient for ESPMinerWebAPI {
68 async fn send_command(
70 &self,
71 command: &str,
72 _privileged: bool,
73 parameters: Option<Value>,
74 method: Method,
75 ) -> Result<Value> {
76 let url = format!("http://{}:{}/api/{}", self.ip, self.port, command);
77
78 for attempt in 0..=self.retries {
79 let result = self
80 .execute_request(&url, &method, parameters.clone())
81 .await;
82
83 match result {
84 Ok(response) => {
85 if response.status().is_success() {
86 match response.json().await {
87 Ok(json_data) => return Ok(json_data),
88 Err(e) => {
89 if attempt == self.retries {
90 return Err(ESPMinerError::ParseError(e.to_string()))?;
91 }
92 }
93 }
94 } else if attempt == self.retries {
95 return Err(ESPMinerError::HttpError(response.status().as_u16()))?;
96 }
97 }
98 Err(e) => {
99 if attempt == self.retries {
100 return Err(e)?;
101 }
102 }
103 }
104 }
105
106 Err(ESPMinerError::MaxRetriesExceeded)?
107 }
108}
109
110impl ESPMiner200WebAPI for ESPMinerWebAPI {}
111
112impl ESPMinerWebAPI {
113 pub fn new(ip: IpAddr, port: u16) -> Self {
115 let client = Client::builder()
116 .timeout(Duration::from_secs(10))
117 .build()
118 .expect("Failed to create HTTP client");
119
120 Self {
121 client,
122 ip,
123 port,
124 timeout: Duration::from_secs(5),
125 retries: 1,
126 }
127 }
128
129 pub fn with_timeout(mut self, timeout: Duration) -> Self {
131 self.timeout = timeout;
132 self
133 }
134
135 pub fn with_retries(mut self, retries: u32) -> Self {
137 self.retries = retries;
138 self
139 }
140
141 async fn execute_request(
143 &self,
144 url: &str,
145 method: &Method,
146 parameters: Option<Value>,
147 ) -> Result<Response, ESPMinerError> {
148 let request_builder = match *method {
149 Method::GET => self.client.get(url),
150 Method::POST => {
151 let mut builder = self.client.post(url);
152 if let Some(params) = parameters {
153 builder = builder.json(¶ms);
154 }
155 builder
156 }
157 Method::PATCH => {
158 let mut builder = self.client.patch(url);
159 if let Some(params) = parameters {
160 builder = builder.json(¶ms);
161 }
162 builder
163 }
164 _ => return Err(ESPMinerError::UnsupportedMethod(method.to_string())),
165 };
166
167 let request = request_builder
168 .timeout(self.timeout)
169 .build()
170 .map_err(|e| ESPMinerError::RequestError(e.to_string()))?;
171
172 let response = timeout(self.timeout, self.client.execute(request))
173 .await
174 .map_err(|_| ESPMinerError::Timeout)?
175 .map_err(|e| ESPMinerError::NetworkError(e.to_string()))?;
176 Ok(response)
177 }
178}
179
180#[derive(Debug, Clone)]
182pub enum ESPMinerError {
183 NetworkError(String),
185 HttpError(u16),
187 ParseError(String),
189 RequestError(String),
191 Timeout,
193 UnsupportedMethod(String),
195 MaxRetriesExceeded,
197 WebError,
198}
199
200impl std::fmt::Display for ESPMinerError {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 match self {
203 ESPMinerError::NetworkError(msg) => write!(f, "Network error: {msg}"),
204 ESPMinerError::HttpError(code) => write!(f, "HTTP error: {code}"),
205 ESPMinerError::ParseError(msg) => write!(f, "Parse error: {msg}"),
206 ESPMinerError::RequestError(msg) => write!(f, "Request error: {msg}"),
207 ESPMinerError::Timeout => write!(f, "Request timeout"),
208 ESPMinerError::UnsupportedMethod(method) => write!(f, "Unsupported method: {method}"),
209 ESPMinerError::MaxRetriesExceeded => write!(f, "Maximum retries exceeded"),
210 ESPMinerError::WebError => write!(f, "Web error"),
211 }
212 }
213}
214
215impl std::error::Error for ESPMinerError {}
216
217#[cfg(test)]
219mod tests {
220 }