asic_rs/miners/backends/btminer/v2/
rpc.rs1use crate::miners::api::RPCAPIClient;
2use crate::miners::api::rpc::status::RPCCommandStatus;
3use crate::miners::api::{APIClient, rpc::errors::RPCError};
4use crate::miners::commands::MinerCommand;
5use anyhow::{Result, anyhow};
6use async_trait::async_trait;
7use serde_json::{Value, json};
8use std::net::IpAddr;
9use tokio::io::{AsyncReadExt, AsyncWriteExt};
10
11#[derive(Debug)]
12pub struct BTMinerRPCAPI {
13 ip: IpAddr,
14 port: u16,
15}
16
17#[async_trait]
18impl APIClient for BTMinerRPCAPI {
19 async fn get_api_result(&self, command: &MinerCommand) -> Result<Value> {
20 match command {
21 MinerCommand::RPC {
22 command,
23 parameters,
24 } => self
25 .send_command(command, false, parameters.clone())
26 .await
27 .map_err(|e| anyhow!(e.to_string())),
28 _ => Err(anyhow!("Cannot send non RPC command to RPC API")),
29 }
30 }
31}
32
33impl RPCCommandStatus {
34 fn from_btminer_v2(response: &str) -> Result<Self, RPCError> {
35 let parsed: Result<serde_json::Value, _> = serde_json::from_str(response);
36
37 if let Ok(data) = &parsed {
38 let command_status = data["STATUS"][0]["STATUS"]
39 .as_str()
40 .or(data["STATUS"].as_str());
41 let message = data["STATUS"][0]["Msg"].as_str().or(data["Msg"].as_str());
42
43 match command_status {
44 Some(status) => match status {
45 "S" | "I" => Ok(RPCCommandStatus::Success),
46 _ => Err(RPCError::StatusCheckFailed(
47 message
48 .unwrap_or("Unknown error when looking for status code")
49 .to_owned(),
50 )),
51 },
52 None => Err(RPCError::StatusCheckFailed(
53 message
54 .unwrap_or("Unknown error when parsing status")
55 .to_owned(),
56 )),
57 }
58 } else {
59 Err(RPCError::DeserializationFailed(parsed.err().unwrap()))
60 }
61 }
62}
63
64#[async_trait]
65impl RPCAPIClient for BTMinerRPCAPI {
66 async fn send_command(
67 &self,
68 command: &str,
69 _privileged: bool,
70 parameters: Option<Value>,
71 ) -> Result<Value> {
72 let mut stream = tokio::net::TcpStream::connect((self.ip, self.port))
73 .await
74 .map_err(|_| RPCError::ConnectionFailed)?;
75
76 let request = match parameters {
77 Some(Value::Object(mut obj)) => {
78 obj.insert("command".to_string(), json!(command));
80 Value::Object(obj)
81 }
82 Some(other) => {
83 json!({ "command": command, "paramater": other })
85 }
86 None => {
87 json!({ "command": command })
89 }
90 };
91 let json_str = request.to_string();
92 let json_bytes = json_str.as_bytes();
93
94 stream.write_all(json_bytes).await.unwrap();
95
96 let mut buffer = Vec::new();
97 stream.read_to_end(&mut buffer).await.unwrap();
98
99 let response = String::from_utf8_lossy(&buffer)
100 .into_owned()
101 .replace('\0', "");
102
103 self.parse_rpc_result(&response)
104 }
105}
106
107impl BTMinerRPCAPI {
108 pub fn new(ip: IpAddr, port: Option<u16>) -> Self {
109 Self {
110 ip,
111 port: port.unwrap_or(4028),
112 }
113 }
114
115 fn parse_rpc_result(&self, response: &str) -> Result<Value> {
116 let status = RPCCommandStatus::from_btminer_v2(response)?;
117 match status.into_result() {
118 Ok(_) => Ok(serde_json::from_str(response)?),
119 Err(e) => Err(e)?,
120 }
121 }
122}