1#![no_std]
13
14extern crate alloc;
15
16use alloc::format;
17use alloc::string::{String, ToString};
18use alloc::vec::Vec;
19use core::fmt;
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum RolandError {
24 SyntaxError,
26 Invalid,
28 OutOfRange,
30 NoStx,
32 UnknownError(u8),
34 InvalidAddress,
36 InvalidValue,
38 InvalidResponse,
40}
41
42impl fmt::Display for RolandError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 RolandError::SyntaxError => write!(f, "Syntax error in received command"),
46 RolandError::Invalid => write!(f, "Invalid command due to other settings"),
47 RolandError::OutOfRange => write!(f, "Parameter out of range"),
48 RolandError::NoStx => write!(f, "Missing STX at command start"),
49 RolandError::UnknownError(code) => write!(f, "Unknown error code: {}", code),
50 RolandError::InvalidAddress => write!(f, "Invalid address format"),
51 RolandError::InvalidValue => write!(f, "Invalid value format"),
52 RolandError::InvalidResponse => write!(f, "Invalid response format"),
53 }
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59pub struct Address {
60 pub high: u8,
62 pub mid: u8,
64 pub low: u8,
66}
67
68impl Address {
69 pub fn new(high: u8, mid: u8, low: u8) -> Self {
71 Self { high, mid, low }
72 }
73
74 pub fn from_hex(hex: &str) -> Result<Self, RolandError> {
85 if hex.len() != 6 {
86 return Err(RolandError::InvalidAddress);
87 }
88
89 let high = parse_hex_byte(&hex[0..2])?;
91 let mid = parse_hex_byte(&hex[2..4])?;
92 let low = parse_hex_byte(&hex[4..6])?;
93
94 Ok(Self { high, mid, low })
95 }
96
97 pub fn to_hex(&self) -> String {
101 format!("{:02X}{:02X}{:02X}", self.high, self.mid, self.low)
102 }
103
104 pub fn write_hex<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
109 write_hex_byte(w, self.high)?;
110 write_hex_byte(w, self.mid)?;
111 write_hex_byte(w, self.low)
112 }
113}
114
115fn parse_hex_byte(s: &str) -> Result<u8, RolandError> {
117 if s.len() != 2 {
118 return Err(RolandError::InvalidAddress);
119 }
120
121 let mut result = 0u8;
122 for ch in s.chars() {
123 let digit = match ch {
124 '0'..='9' => ch as u8 - b'0',
125 'A'..='F' => ch as u8 - b'A' + 10,
126 'a'..='f' => ch as u8 - b'a' + 10,
127 _ => return Err(RolandError::InvalidAddress),
128 };
129 result = result * 16 + digit;
130 }
131 Ok(result)
132}
133
134fn write_hex_byte<W: fmt::Write>(w: &mut W, byte: u8) -> fmt::Result {
136 let high = (byte >> 4) & 0x0F;
137 let low = byte & 0x0F;
138
139 let high_char = if high < 10 {
140 (b'0' + high) as char
141 } else {
142 (b'A' + high - 10) as char
143 };
144
145 let low_char = if low < 10 {
146 (b'0' + low) as char
147 } else {
148 (b'A' + low - 10) as char
149 };
150
151 w.write_char(high_char)?;
152 w.write_char(low_char)
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
157pub enum Command {
158 WriteParameter {
160 address: Address,
162 value: u8,
164 },
165 ReadParameter {
167 address: Address,
169 size: u32,
171 },
172 GetVersion,
174}
175
176impl Command {
177 pub fn encode(&self) -> String {
184 match self {
185 Command::WriteParameter { address, value } => {
186 format!("DTH:{},{:02X};", address.to_hex(), value)
187 }
188 Command::ReadParameter { address, size } => {
189 let size_hex = format!("{:06X}", size);
191 format!("RQH:{},{};", address.to_hex(), size_hex)
192 }
193 Command::GetVersion => "VER;".to_string(),
194 }
195 }
196
197 pub fn encode_with_stx(&self) -> String {
201 format!("\x02{}", self.encode())
202 }
203
204 pub fn write<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
209 match self {
210 Command::WriteParameter { address, value } => {
211 w.write_str("DTH:")?;
212 address.write_hex(w)?;
213 w.write_str(",")?;
214 write_hex_byte(w, *value)?;
215 w.write_str(";")
216 }
217 Command::ReadParameter { address, size } => {
218 w.write_str("RQH:")?;
219 address.write_hex(w)?;
220 w.write_str(",")?;
221 write_hex_u24(w, *size)?;
223 w.write_str(";")
224 }
225 Command::GetVersion => w.write_str("VER;"),
226 }
227 }
228
229 pub fn write_with_stx<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
231 w.write_char('\x02')?;
232 self.write(w)
233 }
234}
235
236fn write_hex_u24<W: fmt::Write>(w: &mut W, value: u32) -> fmt::Result {
238 write_hex_byte(w, ((value >> 16) & 0xFF) as u8)?;
239 write_hex_byte(w, ((value >> 8) & 0xFF) as u8)?;
240 write_hex_byte(w, (value & 0xFF) as u8)
241}
242
243#[derive(Debug, Clone, PartialEq, Eq)]
245pub enum Response {
246 Acknowledge,
248 Data {
250 address: Address,
252 value: u8,
254 },
255 Version {
257 product: String,
259 version: String,
261 },
262 Error(RolandError),
264}
265
266impl Response {
267 pub fn parse(response: &str) -> Result<Self, RolandError> {
273 let response = response.trim();
274
275 let response = response.strip_prefix('\x02').unwrap_or(response);
277
278 if response == "\x06" || response == "ack" {
280 return Ok(Response::Acknowledge);
281 }
282
283 if response == "\x11" || response == "xon" {
286 return Err(RolandError::InvalidResponse);
288 }
289 if response == "\x13" || response == "xoff" {
290 return Err(RolandError::InvalidResponse);
292 }
293
294 if let Some(content) = response.strip_prefix("DTH:") {
296 if !content.ends_with(';') {
297 return Err(RolandError::InvalidResponse);
298 }
299 let content = &content[..content.len() - 1];
300 let parts: Vec<&str> = content.split(',').collect();
301 if parts.len() != 2 {
302 return Err(RolandError::InvalidResponse);
303 }
304 let address = Address::from_hex(parts[0])?;
305 let value = parse_hex_byte(parts[1])?;
306 return Ok(Response::Data { address, value });
307 }
308
309 if let Some(content) = response.strip_prefix("VER:") {
311 if !content.ends_with(';') {
312 return Err(RolandError::InvalidResponse);
313 }
314 let content = &content[..content.len() - 1];
315 let parts: Vec<&str> = content.split(',').collect();
316 if parts.len() != 2 {
317 return Err(RolandError::InvalidResponse);
318 }
319 return Ok(Response::Version {
320 product: parts[0].to_string(),
321 version: parts[1].to_string(),
322 });
323 }
324
325 if let Some(content) = response.strip_prefix("ERR:") {
327 if !content.ends_with(';') {
328 return Err(RolandError::InvalidResponse);
329 }
330 let content = &content[..content.len() - 1];
331 let code = parse_decimal_u8(content)?;
332 let error = match code {
333 0 => RolandError::SyntaxError,
334 4 => RolandError::Invalid,
335 5 => RolandError::OutOfRange,
336 6 => RolandError::NoStx,
337 _ => RolandError::UnknownError(code),
338 };
339 return Ok(Response::Error(error));
340 }
341
342 Err(RolandError::InvalidResponse)
343 }
344}
345
346fn parse_decimal_u8(s: &str) -> Result<u8, RolandError> {
348 let mut result = 0u8;
349 for ch in s.chars() {
350 let digit = match ch {
351 '0'..='9' => ch as u8 - b'0',
352 _ => return Err(RolandError::InvalidResponse),
353 };
354 result = result
355 .checked_mul(10)
356 .and_then(|r| r.checked_add(digit))
357 .ok_or(RolandError::InvalidResponse)?;
358 }
359 Ok(result)
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_address_from_hex() {
368 let addr = Address::from_hex("123456").unwrap();
369 assert_eq!(addr.high, 0x12);
370 assert_eq!(addr.mid, 0x34);
371 assert_eq!(addr.low, 0x56);
372 }
373
374 #[test]
375 fn test_address_to_hex() {
376 let addr = Address::new(0x12, 0x34, 0x56);
377 assert_eq!(addr.to_hex(), "123456");
378 }
379
380 #[test]
381 fn test_address_write_hex() {
382 let addr = Address::new(0x12, 0x34, 0x56);
383 let mut s = String::new();
384 addr.write_hex(&mut s).unwrap();
385 assert_eq!(s, "123456");
386 }
387
388 #[test]
389 fn test_write_command() {
390 let cmd = Command::WriteParameter {
391 address: Address::from_hex("123456").unwrap(),
392 value: 0x01,
393 };
394 assert_eq!(cmd.encode(), "DTH:123456,01;");
395 }
396
397 #[test]
398 fn test_write_command_write() {
399 let cmd = Command::WriteParameter {
400 address: Address::from_hex("123456").unwrap(),
401 value: 0x01,
402 };
403 let mut s = String::new();
404 cmd.write(&mut s).unwrap();
405 assert_eq!(s, "DTH:123456,01;");
406 }
407
408 #[test]
409 fn test_read_command() {
410 let cmd = Command::ReadParameter {
411 address: Address::from_hex("123456").unwrap(),
412 size: 1,
413 };
414 assert_eq!(cmd.encode(), "RQH:123456,000001;");
415 }
416
417 #[test]
418 fn test_version_command() {
419 let cmd = Command::GetVersion;
420 assert_eq!(cmd.encode(), "VER;");
421 }
422
423 #[test]
424 fn test_parse_ack() {
425 let resp = Response::parse("\x06").unwrap();
426 assert_eq!(resp, Response::Acknowledge);
427 }
428
429 #[test]
430 fn test_parse_data() {
431 let resp = Response::parse("DTH:123456,01;").unwrap();
432 match resp {
433 Response::Data { address, value } => {
434 assert_eq!(address.to_hex(), "123456");
435 assert_eq!(value, 0x01);
436 }
437 _ => panic!("Expected Data response"),
438 }
439 }
440
441 #[test]
442 fn test_parse_version() {
443 let resp = Response::parse("VER:VR-6HD,1.00;").unwrap();
444 match resp {
445 Response::Version { product, version } => {
446 assert_eq!(product, "VR-6HD");
447 assert_eq!(version, "1.00");
448 }
449 _ => panic!("Expected Version response"),
450 }
451 }
452
453 #[test]
454 fn test_parse_error() {
455 let resp = Response::parse("ERR:0;").unwrap();
456 match resp {
457 Response::Error(RolandError::SyntaxError) => {}
458 _ => panic!("Expected SyntaxError"),
459 }
460 }
461}