asic_rs/miners/backends/btminer/v1/
rpc.rs

1use 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_v1(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                // Use the existing object as the base
79                obj.insert("command".to_string(), json!(command));
80                Value::Object(obj)
81            }
82            Some(other) => {
83                // Wrap non-objects into the "param" key
84                json!({ "command": command, "paramater": other })
85            }
86            None => {
87                // No parameters at all
88                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_v1(response)?;
117        match status.into_result() {
118            Ok(_) => Ok(serde_json::from_str(response)?),
119            Err(e) => Err(e)?,
120        }
121    }
122}