telnet_client/
telnet_client.rs

1//! Example: Telnet client for Roland VR-6HD
2//!
3//! This example demonstrates how to use the roland-core library
4//! to communicate with a VR-6HD device via Telnet.
5
6use roland_core::{Address, Command, Response, RolandError};
7use std::io::{Read, Write};
8use std::net::TcpStream;
9use std::time::Duration;
10
11/// Error type for Telnet client
12#[derive(Debug)]
13pub enum TelnetError {
14    /// Protocol-level error from roland-core
15    Protocol(RolandError),
16    /// I/O error
17    Io(std::io::Error),
18    /// Connection closed
19    ConnectionClosed,
20}
21
22impl std::fmt::Display for TelnetError {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        match self {
25            TelnetError::Protocol(e) => write!(f, "Protocol error: {}", e),
26            TelnetError::Io(e) => write!(f, "I/O error: {}", e),
27            TelnetError::ConnectionClosed => write!(f, "Connection closed"),
28        }
29    }
30}
31
32impl std::error::Error for TelnetError {}
33
34impl From<RolandError> for TelnetError {
35    fn from(e: RolandError) -> Self {
36        TelnetError::Protocol(e)
37    }
38}
39
40impl From<std::io::Error> for TelnetError {
41    fn from(e: std::io::Error) -> Self {
42        TelnetError::Io(e)
43    }
44}
45
46/// Telnet client for Roland VR-6HD
47pub struct TelnetClient {
48    stream: TcpStream,
49    buffer: Vec<u8>,
50}
51
52impl TelnetClient {
53    /// Connect to VR-6HD device via Telnet
54    ///
55    /// # Arguments
56    /// * `host` - IP address or hostname of the VR-6HD device
57    /// * `port` - Telnet port (default: 23)
58    ///
59    /// # Returns
60    /// * `Result<Self, TelnetError>` - Connected client or error
61    pub fn connect(host: &str, port: u16) -> Result<Self, TelnetError> {
62        let addr = format!("{}:{}", host, port);
63        let stream = TcpStream::connect(&addr)?;
64
65        // Set read timeout
66        stream.set_read_timeout(Some(Duration::from_secs(5)))?;
67
68        // Set write timeout
69        stream.set_write_timeout(Some(Duration::from_secs(5)))?;
70
71        Ok(Self {
72            stream,
73            buffer: Vec::new(),
74        })
75    }
76
77    /// Send a command and wait for response
78    ///
79    /// # Arguments
80    /// * `command` - Command to send
81    ///
82    /// # Returns
83    /// * `Result<Response, TelnetError>` - Response from device or error
84    pub fn send_command(&mut self, command: &Command) -> Result<Response, TelnetError> {
85        // Encode command (without STX for Telnet)
86        let cmd_str = command.encode();
87        let cmd_bytes = cmd_str.as_bytes();
88
89        // Send command
90        self.stream.write_all(cmd_bytes)?;
91        self.stream.flush()?;
92
93        // Read response
94        self.read_response()
95    }
96
97    /// Read response from device
98    fn read_response(&mut self) -> Result<Response, TelnetError> {
99        let mut buf = [0u8; 1024];
100
101        // Read data
102        let n = self.stream.read(&mut buf)?;
103
104        if n == 0 {
105            return Err(TelnetError::ConnectionClosed);
106        }
107
108        // Append to buffer
109        self.buffer.extend_from_slice(&buf[..n]);
110
111        // Try to parse response
112        // Responses typically end with ';' or control characters
113        let response_str = String::from_utf8_lossy(&self.buffer);
114
115        // Look for complete response (ends with ';' or is a control character)
116        if response_str.ends_with(';') ||
117           response_str.contains('\x06') || // ACK
118           response_str.contains('\x11') || // XON
119           response_str.contains('\x13')
120        {
121            // XOFF
122            let response = Response::parse(&response_str)?;
123            self.buffer.clear();
124            Ok(response)
125        } else {
126            // Incomplete response, wait a bit and try again
127            std::thread::sleep(Duration::from_millis(100));
128            self.read_response()
129        }
130    }
131
132    /// Write a parameter value
133    ///
134    /// # Arguments
135    /// * `address` - SysEx address (3 bytes as hex string, e.g., "123456")
136    /// * `value` - Value to write (0-255)
137    ///
138    /// # Returns
139    /// * `Result<(), TelnetError>` - Success or error
140    pub fn write_parameter(&mut self, address: &str, value: u8) -> Result<(), TelnetError> {
141        let addr = Address::from_hex(address)?;
142        let cmd = Command::WriteParameter {
143            address: addr,
144            value,
145        };
146        let response = self.send_command(&cmd)?;
147
148        match response {
149            Response::Acknowledge => Ok(()),
150            Response::Error(e) => Err(TelnetError::Protocol(e)),
151            _ => Err(TelnetError::Protocol(RolandError::InvalidResponse)),
152        }
153    }
154
155    /// Read a parameter value
156    ///
157    /// # Arguments
158    /// * `address` - SysEx address (3 bytes as hex string, e.g., "123456")
159    /// * `size` - Size to read (typically 1 for single byte)
160    ///
161    /// # Returns
162    /// * `Result<u8, TelnetError>` - Parameter value or error
163    pub fn read_parameter(&mut self, address: &str, size: u32) -> Result<u8, TelnetError> {
164        let addr = Address::from_hex(address)?;
165        let cmd = Command::ReadParameter {
166            address: addr,
167            size,
168        };
169        let response = self.send_command(&cmd)?;
170
171        match response {
172            Response::Data { value, .. } => Ok(value),
173            Response::Error(e) => Err(TelnetError::Protocol(e)),
174            _ => Err(TelnetError::Protocol(RolandError::InvalidResponse)),
175        }
176    }
177
178    /// Get version information
179    ///
180    /// # Returns
181    /// * `Result<(String, String), TelnetError>` - (product, version) or error
182    pub fn get_version(&mut self) -> Result<(String, String), TelnetError> {
183        let cmd = Command::GetVersion;
184        let response = self.send_command(&cmd)?;
185
186        match response {
187            Response::Version { product, version } => Ok((product, version)),
188            Response::Error(e) => Err(TelnetError::Protocol(e)),
189            _ => Err(TelnetError::Protocol(RolandError::InvalidResponse)),
190        }
191    }
192}
193
194fn main() -> Result<(), Box<dyn std::error::Error>> {
195    // Example usage
196    let args: Vec<String> = std::env::args().collect();
197    if args.len() < 2 {
198        eprintln!("Usage: {} <ip_address> [port]", args[0]);
199        eprintln!("Example: {} 192.168.1.100", args[0]);
200        std::process::exit(1);
201    }
202
203    let host = &args[1];
204    let port = args.get(2).and_then(|p| p.parse().ok()).unwrap_or(23);
205
206    println!("Connecting to {}:{}...", host, port);
207
208    let mut client = TelnetClient::connect(host, port)?;
209    println!("Connected!");
210
211    // Get version information
212    println!("\nGetting version information...");
213    match client.get_version() {
214        Ok((product, version)) => {
215            println!("Product: {}", product);
216            println!("Version: {}", version);
217        }
218        Err(e) => {
219            eprintln!("Error getting version: {}", e);
220        }
221    }
222
223    // Example: Read a parameter (address 00 00 00 = 0x000000)
224    println!("\nReading parameter at address 000000...");
225    match client.read_parameter("000000", 1) {
226        Ok(value) => {
227            println!("Value: 0x{:02X} ({})", value, value);
228        }
229        Err(e) => {
230            eprintln!("Error reading parameter: {}", e);
231        }
232    }
233
234    // Example: Write a parameter
235    // Note: Be careful with actual addresses - this is just an example
236    // println!("\nWriting parameter at address 000000...");
237    // match client.write_parameter("000000", 0x01) {
238    //     Ok(()) => {
239    //         println!("Parameter written successfully");
240    //     }
241    //     Err(e) => {
242    //         eprintln!("Error writing parameter: {}", e);
243    //     }
244    // }
245
246    Ok(())
247}