hamlib_client/
lib.rs

1/*
2 * Copyright (C) 2024 Luca Cireddu <sardylan@gmail.com>
3 *
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License as published by the Free Software
6 * Foundation, version 3.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License along with
13 * this program. If not, see <https://www.gnu.org/licenses/>.
14 *
15 */
16
17pub mod error;
18pub mod mode;
19pub mod vfo;
20pub mod commands;
21pub mod adif;
22
23use crate::commands::{get_freq, get_info, get_mode, get_split_freq, get_split_mode, get_split_vfo, get_vfo};
24use crate::error::RigCtlError;
25use crate::vfo::VFO;
26use std::time::Duration;
27use tokio::io::{AsyncReadExt, AsyncWriteExt};
28use tokio::net::TcpStream;
29use tokio::time;
30
31pub struct RigCtlClient {
32    host: String,
33    port: u16,
34    stream: Option<TcpStream>,
35    timeout: Duration,
36}
37
38impl RigCtlClient {
39    pub fn new(host: &str, port: u16, timeout: Option<u64>) -> Self {
40        Self {
41            host: String::from(host),
42            port,
43            stream: None,
44            timeout: Duration::from_millis(timeout.unwrap_or(1000)),
45        }
46    }
47
48    pub async fn connect(&mut self) -> Result<(), RigCtlError> {
49        if self.is_connected() {
50            return Err(RigCtlError::AlreadyConnected);
51        }
52
53        let connection_string = format!("{}:{}", self.host, self.port);
54
55        let stream = TcpStream::connect(connection_string).await?;
56        self.stream = Some(stream);
57
58        Ok(())
59    }
60
61    pub fn disconnect(&mut self) {
62        if !self.is_connected() {
63            return;
64        }
65
66        self.stream = None;
67    }
68
69    pub fn is_connected(&self) -> bool {
70        self.stream.is_some()
71    }
72
73    pub fn set_communication_timeout(&mut self, timeout: u64) {
74        self.timeout = Duration::from_millis(timeout);
75    }
76
77    pub async fn get_info(&mut self) -> Result<get_info::Response, RigCtlError> {
78        let cmd = "get_info".to_string();
79        let response = self.execute_command(&cmd).await?;
80        get_info::parse(&response)
81    }
82
83    pub async fn get_mode(&mut self, vfo: VFO) -> Result<get_mode::Response, RigCtlError> {
84        let cmd = format!("get_mode {}", vfo);
85        let response = self.execute_command(&cmd).await?;
86        get_mode::parse(&response)
87    }
88
89    pub async fn get_freq(&mut self, vfo: VFO) -> Result<get_freq::Response, RigCtlError> {
90        let cmd = format!("get_freq {}", vfo);
91        let response = self.execute_command(&cmd).await?;
92        get_freq::parse(&response)
93    }
94
95    pub async fn get_vfo(&mut self) -> Result<get_vfo::Response, RigCtlError> {
96        let cmd = "get_vfo".to_string();
97        let response = self.execute_command(&cmd).await?;
98        get_vfo::parse(&response)
99    }
100
101    pub async fn get_split_vfo(&mut self) -> Result<get_split_vfo::Response, RigCtlError> {
102        let cmd = "get_split_vfo 0".to_string();
103        let response = self.execute_command(&cmd).await?;
104        get_split_vfo::parse(&response)
105    }
106
107    pub async fn get_split_mode(&mut self, vfo: VFO) -> Result<get_split_mode::Response, RigCtlError> {
108        let cmd = format!("get_split_mode {}", vfo);
109        let response = self.execute_command(&cmd).await?;
110        get_split_mode::parse(&response)
111    }
112
113    pub async fn get_split_freq(&mut self, vfo: VFO) -> Result<get_split_freq::Response, RigCtlError> {
114        let cmd = format!("get_split_freq {}", vfo);
115        let response = self.execute_command(&cmd).await?;
116        get_split_freq::parse(&response)
117    }
118
119    fn compose_command(&self, command: &str) -> String {
120        format!("|\\{}", command)
121    }
122
123    async fn execute_command(&mut self, command: &str) -> Result<String, RigCtlError> {
124        let cmd = self.compose_command(&command);
125        self.write_line(&cmd).await?;
126        self.read_line().await
127    }
128
129    async fn read_line(&mut self) -> Result<String, RigCtlError> {
130        log::debug!("Reading line");
131
132        let mut buf = [0u8; 4096];
133        let bytes_read = time::timeout(
134            self.timeout,
135            self.stream.
136                as_mut().unwrap()
137                .read(&mut buf))
138            .await
139            .map_err(|_| RigCtlError::CommunicationTimeout)??;
140
141        let line = String::from_utf8(buf[0..bytes_read].to_owned())?
142            .trim_end().to_string();
143
144        log::trace!(" <<< [{}] ({} bytes)", line, line.len());
145
146        Ok(line)
147    }
148
149    async fn write_line(&mut self, data: &str) -> Result<(), RigCtlError> {
150        log::debug!("Writing line");
151        log::trace!(" >>> [{}] ({} bytes)", data, data.len());
152        time::timeout(
153            self.timeout,
154            self.stream
155                .as_mut().unwrap()
156                .write_all(format!("{}\n", data).as_bytes()))
157            .await
158            .map_err(|_| RigCtlError::CommunicationTimeout)??;
159        Ok(())
160    }
161}