roland_rs/
lib.rs

1//! Rust library for Roland VR-6HD remote control
2//!
3//! This library provides a high-level API for communicating with
4//! Roland VR-6HD devices via Telnet (std environment).
5
6pub use roland_core::*;
7
8use roland_core::{Address, Command, Response, RolandError};
9use std::io::{Read, Write};
10use std::net::TcpStream;
11use std::time::Duration;
12
13/// Error type for Telnet client
14#[derive(Debug)]
15pub enum TelnetError {
16    /// Protocol-level error from roland-core
17    Protocol(RolandError),
18    /// I/O error
19    Io(std::io::Error),
20    /// Connection closed
21    ConnectionClosed,
22}
23
24impl std::fmt::Display for TelnetError {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            TelnetError::Protocol(e) => write!(f, "Protocol error: {}", e),
28            TelnetError::Io(e) => write!(f, "I/O error: {}", e),
29            TelnetError::ConnectionClosed => write!(f, "Connection closed"),
30        }
31    }
32}
33
34impl std::error::Error for TelnetError {}
35
36impl From<RolandError> for TelnetError {
37    fn from(e: RolandError) -> Self {
38        TelnetError::Protocol(e)
39    }
40}
41
42impl From<std::io::Error> for TelnetError {
43    fn from(e: std::io::Error) -> Self {
44        TelnetError::Io(e)
45    }
46}
47
48/// Telnet client for Roland VR-6HD
49pub struct TelnetClient {
50    stream: TcpStream,
51    buffer: Vec<u8>,
52}
53
54impl TelnetClient {
55    /// Connect to VR-6HD device via Telnet
56    ///
57    /// # Arguments
58    /// * `host` - IP address or hostname of the VR-6HD device
59    /// * `port` - Telnet port (default: 23)
60    ///
61    /// # Returns
62    /// * `Result<Self, TelnetError>` - Connected client or error
63    pub fn connect(host: &str, port: u16) -> Result<Self, TelnetError> {
64        let addr = format!("{}:{}", host, port);
65        let stream = TcpStream::connect(&addr)?;
66
67        // Set read timeout
68        stream.set_read_timeout(Some(Duration::from_secs(5)))?;
69
70        // Set write timeout
71        stream.set_write_timeout(Some(Duration::from_secs(5)))?;
72
73        Ok(Self {
74            stream,
75            buffer: Vec::new(),
76        })
77    }
78
79    /// Send a command and wait for response
80    ///
81    /// # Arguments
82    /// * `command` - Command to send
83    ///
84    /// # Returns
85    /// * `Result<Response, TelnetError>` - Response from device or error
86    pub fn send_command(&mut self, command: &Command) -> Result<Response, TelnetError> {
87        // Encode command (without STX for Telnet)
88        let cmd_str = command.encode();
89        let cmd_bytes = cmd_str.as_bytes();
90
91        // Send command
92        self.stream.write_all(cmd_bytes)?;
93        self.stream.flush()?;
94
95        // Read response
96        self.read_response()
97    }
98
99    /// Read response from device
100    fn read_response(&mut self) -> Result<Response, TelnetError> {
101        let mut buf = [0u8; 1024];
102
103        // Read data
104        let n = self.stream.read(&mut buf)?;
105
106        if n == 0 {
107            return Err(TelnetError::ConnectionClosed);
108        }
109
110        // Append to buffer
111        self.buffer.extend_from_slice(&buf[..n]);
112
113        // Try to parse response
114        // Responses typically end with ';' or control characters
115        let response_str = String::from_utf8_lossy(&self.buffer);
116
117        // Look for complete response (ends with ';' or is a control character)
118        if response_str.ends_with(';') ||
119           response_str.contains('\x06') || // ACK
120           response_str.contains('\x11') || // XON
121           response_str.contains('\x13')
122        {
123            // XOFF
124            let response = Response::parse(&response_str)?;
125            self.buffer.clear();
126            Ok(response)
127        } else {
128            // Incomplete response, wait a bit and try again
129            std::thread::sleep(Duration::from_millis(100));
130            self.read_response()
131        }
132    }
133
134    /// Write a parameter value
135    ///
136    /// # Arguments
137    /// * `address` - SysEx address (3 bytes as hex string, e.g., "123456")
138    /// * `value` - Value to write (0-255)
139    ///
140    /// # Returns
141    /// * `Result<(), TelnetError>` - Success or error
142    pub fn write_parameter(&mut self, address: &str, value: u8) -> Result<(), TelnetError> {
143        let addr = Address::from_hex(address)?;
144        let cmd = Command::WriteParameter {
145            address: addr,
146            value,
147        };
148        let response = self.send_command(&cmd)?;
149
150        match response {
151            Response::Acknowledge => Ok(()),
152            Response::Error(e) => Err(TelnetError::Protocol(e)),
153            _ => Err(TelnetError::Protocol(RolandError::InvalidResponse)),
154        }
155    }
156
157    /// Read a parameter value
158    ///
159    /// # Arguments
160    /// * `address` - SysEx address (3 bytes as hex string, e.g., "123456")
161    /// * `size` - Size to read (typically 1 for single byte)
162    ///
163    /// # Returns
164    /// * `Result<u8, TelnetError>` - Parameter value or error
165    pub fn read_parameter(&mut self, address: &str, size: u32) -> Result<u8, TelnetError> {
166        let addr = Address::from_hex(address)?;
167        let cmd = Command::ReadParameter {
168            address: addr,
169            size,
170        };
171        let response = self.send_command(&cmd)?;
172
173        match response {
174            Response::Data { value, .. } => Ok(value),
175            Response::Error(e) => Err(TelnetError::Protocol(e)),
176            _ => Err(TelnetError::Protocol(RolandError::InvalidResponse)),
177        }
178    }
179
180    /// Get version information
181    ///
182    /// # Returns
183    /// * `Result<(String, String), TelnetError>` - (product, version) or error
184    pub fn get_version(&mut self) -> Result<(String, String), TelnetError> {
185        let cmd = Command::GetVersion;
186        let response = self.send_command(&cmd)?;
187
188        match response {
189            Response::Version { product, version } => Ok((product, version)),
190            Response::Error(e) => Err(TelnetError::Protocol(e)),
191            _ => Err(TelnetError::Protocol(RolandError::InvalidResponse)),
192        }
193    }
194}