diff --git a/Cargo.toml b/Cargo.toml index cc090d9..99efa0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ description = "CCTalk protocol implementation for handling payment devices" edition = "2018" [dependencies] -serialport = "4" +serialport = {version = "4.0.1", default-features = false} log = "0.4" [[example]] @@ -19,4 +19,4 @@ name = "cctalk-host" [dev-dependencies] env_logger = "0.7" -clap = "2.33.3" +clap = "3.1" diff --git a/examples/cctalk-host/main.rs b/examples/cctalk-host/main.rs index 572bb37..e612aff 100644 --- a/examples/cctalk-host/main.rs +++ b/examples/cctalk-host/main.rs @@ -1,55 +1,99 @@ use cctalk::{device::CCTalkDevice, protocol::ChecksumType}; -use clap::{value_t, App, Arg}; +use clap::{Arg, Command}; use std::time::Duration; const PROGRAM: Option<&'static str> = option_env!("CARGO_PKG_NAME"); const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); const DESCRIPTION: Option<&'static str> = option_env!("CARGO_PKG_DESCRIPTION"); +fn master_inhibit(dev: &str, id: u8, status: Option<&str>) { + let serial = serialport::new(dev, 9600) + .timeout(Duration::from_millis(500)) + .open() + .expect("Failed to open port"); + + let serial_dev = Box::new(cctalk::client::SerialClient::new(serial, 1).unwrap()); + let mut cctalk = CCTalkDevice::new(serial_dev, id, ChecksumType::SimpleChecksum).unwrap(); + + if let Some(b) = status { + let x = match b { + "1" => 1, + _ => 0, + }; + println!("{:?}", cctalk.modify_master_inhibit_status(x).unwrap()); + } +} + +fn request_info(dev: &str, id: u8, field: Option<&str>) { + let serial = serialport::new(dev, 9600) + .timeout(Duration::from_millis(500)) + .open() + .expect("Failed to open port"); + + let serial_dev = Box::new(cctalk::client::SerialClient::new(serial, 1).unwrap()); + + let mut cctalk = CCTalkDevice::new(serial_dev, id, ChecksumType::SimpleChecksum).unwrap(); + + match field { + Some("equipment_category_id") => { + println!("{:?}", cctalk.request_equipment_category().unwrap()); + } + _ => {} + } +} + fn main() { env_logger::init(); - let matches = App::new(PROGRAM.unwrap_or("cctalk-host")) + let matches = Command::new(PROGRAM.unwrap_or("cctalk-host")) .version(VERSION.unwrap_or("unknown")) .about(DESCRIPTION.unwrap_or("")) .arg( - Arg::with_name("serial") - .short("s") + Arg::new("serial") + .short('s') .long("serial") .value_name("DEVICE") - .help("Serial Device for ccTalk host (for example /dev/ttyUSB0 or COM3)") + .help("ccTalk serial device (for example /dev/ttyUSB0 or COM3)") .takes_value(true) .required(true), ) .arg( - Arg::with_name("target") - .short("t") + Arg::new("target") + .short('t') .long("target") .value_name("TARGET_ADDRESS") .help("Address of the client device") .default_value("2"), ) + .subcommand( + Command::new("request") + .arg( + Arg::new("field") + //.possible_values(["manufacturer_id", "equipment_category_id", "serial_number"]) + .possible_values(["equipment_category_id"]), + ) + .about("Request information from device (status, manufacturer id, ...)"), + ) + .subcommand( + Command::new("set_master_inhibit").arg( + Arg::new("state") + .takes_value(true) + .possible_values(["1", "0"]) + .required(true), + ), + ) .get_matches(); let dev = matches.value_of("serial").unwrap(); + let _id: u8 = matches.value_of_t("target").unwrap_or_else(|e| e.exit()); - let target_device_id = value_t!(matches.value_of("target"), u8).unwrap_or_else(|e| e.exit()); - - let serial = serialport::new(dev, 9600) - .timeout(Duration::from_millis(500)) - .open() - .expect("Failed to open port"); - - // As per ccTalk general usage, there is usually single "master" - // which initiates the queries and its address is 1. - let serial_dev = Box::new(cctalk::client::SerialClient::new(serial, 1).unwrap()); - - let mut cctalk = - CCTalkDevice::new(serial_dev, target_device_id, ChecksumType::SimpleChecksum).unwrap(); - - println!("Querying client device: {}", target_device_id); - - let resp = cctalk.request_equipment_category().unwrap(); - - println!("{}", resp); + match matches.subcommand() { + Some(("request", sub_matches)) => request_info(dev, _id, sub_matches.value_of("field")), + Some(("set_master_inhibit", sub_matches)) => { + master_inhibit(dev, _id, sub_matches.value_of("state")); + } + _ => { + println!("Expecting subcommand..."); + } + } } diff --git a/examples/coinacceptor/main.rs b/examples/coinacceptor/main.rs index 551cb92..f406c59 100644 --- a/examples/coinacceptor/main.rs +++ b/examples/coinacceptor/main.rs @@ -2,7 +2,7 @@ use cctalk::{ device::{CoinAcceptor, CoinTable, CoreInfo}, protocol::{ChecksumType, Message}, }; -use clap::{App, Arg}; +use clap::{Command, Arg}; use std::thread; use std::time::Duration; @@ -11,14 +11,15 @@ const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); const DESCRIPTION: Option<&'static str> = option_env!("CARGO_PKG_DESCRIPTION"); fn main() { - env_logger::init(); - let matches = App::new(PROGRAM.unwrap_or("cctalk-emulator")) + env_logger::init(); + + let matches = Command::new(PROGRAM.unwrap_or("cctalk-emulator")) .version(VERSION.unwrap_or("unknown")) .about(DESCRIPTION.unwrap_or("")) .arg( - Arg::with_name("serial") - .short("s") + Arg::new("serial") + .short('s') .long("serial") .value_name("DEVICE") .help("Serial Device for ccTalk") @@ -30,7 +31,7 @@ fn main() { let dev = matches.value_of("serial").unwrap(); let serial = serialport::new(dev, 9600) - .timeout(Duration::from_millis(500)) + .timeout(Duration::from_millis(20)) .open() .expect("Failed to open port"); @@ -69,6 +70,6 @@ fn main() { thread::sleep(Duration::from_millis(20)); } - thread::sleep(Duration::from_millis(100)); + thread::sleep(Duration::from_millis(50)); } } diff --git a/src/client.rs b/src/client.rs index b458b04..1d989cc 100644 --- a/src/client.rs +++ b/src/client.rs @@ -34,12 +34,12 @@ impl convert::From for ClientError { impl Clone for ClientError { fn clone(&self) -> Self { - match self { - &ClientError::CCTalkError(ref e) => ClientError::CCTalkError(e.clone()), - &ClientError::IOError(ref e) => { + match *self { + ClientError::CCTalkError(ref e) => ClientError::CCTalkError(*e), + ClientError::IOError(ref e) => { ClientError::IOError(std::io::Error::new(e.kind(), e.to_string())) } - &ClientError::SerialError(ref e) => { + ClientError::SerialError(ref e) => { ClientError::SerialError(serialport::Error::new(e.kind(), e.to_string())) } } @@ -52,6 +52,7 @@ pub trait CCTalkClient { fn set_bill_event(&mut self, bill_event: BillEvent); fn read_messages(&mut self) -> Result, ClientError>; fn send_message(&mut self, msg: &Message) -> Result<(), ClientError>; + fn clear(&mut self); } pub struct SerialClient { @@ -78,15 +79,16 @@ impl SerialClient { received: &mut Vec, messages: &mut Vec, ) -> Result<(), ClientError> { - // log::debug!("Received: {:?}", received); - self.buffer.append(received); - // log::debug!("Buffer: {:?}", self.buffer); - - // decode will leave the remaining stuff in the buffer - let decode_res = Message::decode(&mut self.buffer); + + // decode will process latest message + let decode_res = Message::decode(received); match decode_res { Ok(message) => { if message.destination == self.address { + log::trace!( + "Received: {:?}", + message, + ); messages.push(message); Ok(()) } else { @@ -126,7 +128,6 @@ impl SerialClient { fn send(&mut self, msg: &Message) -> Result<(), std::io::Error> { let buf: Vec = msg.encode(); - // log::debug!("Sending CCTalk message: {:?}", msg); log::trace!("Sending CCTalk message encoded: {:?}", buf); self.port.write_all(&buf[..]) } @@ -136,10 +137,12 @@ impl SerialClient { let mut counter = 0; - while (messages.len() < 1) && (counter < 80) { + while messages.is_empty() && (counter < 80) { let mut received = self.read_from_serial()?; - // log::debug!("Received on serial: {:?} Counter: {}", received, counter); - self.read_and_decode(&mut received, &mut messages)?; + if !received.is_empty() { + log::trace!("Received on serial: {:?} Counter: {}", received, counter); + self.read_and_decode(&mut received, &mut messages)?; + } counter += 1; } @@ -160,7 +163,7 @@ impl SerialClient { while !timeout { let mut received = self.read_from_serial()?; self.read_and_decode(&mut received, &mut messages)?; - if (received.len() == 0) && (self.buffer.len() == 0) { + if received.is_empty() && self.buffer.is_empty() { timeout = true; } } @@ -169,6 +172,15 @@ impl SerialClient { Ok(messages) } + + /// Clear incoming message buffer together with serial port's + /// input and output buffers. + fn clear(&mut self) { + self.buffer.clear(); + self.port + .clear(serialport::ClearBuffer::All) + .unwrap_or_else(|e| log::error!("Unable to flush serial: {:?}", e)); + } } impl CCTalkClient for SerialClient { @@ -177,14 +189,14 @@ impl CCTalkClient for SerialClient { // log::debug!("Waiting for Reply"); let received = self.read()?; - if received.len() > 0 { - let ref reply = received[0]; + if !received.is_empty() { + let reply = &received[0]; match reply.payload.header { HeaderType::Reply => Ok(reply.payload.clone()), _ => Err(ClientError::CCTalkError(ErrorType::NotAReply)), } } else { - if self.buffer.len() != 0 { + if !self.buffer.is_empty() { log::debug!( "Message not received in time, clearing partial message from buffer: {:?}", self.buffer @@ -202,17 +214,19 @@ impl CCTalkClient for SerialClient { fn set_bill_event(&mut self, _: BillEvent) {} fn read_messages(&mut self) -> Result, ClientError> { - self.read_all(1) + self.read() } fn send_message(&mut self, msg: &Message) -> Result<(), ClientError> { - let send_result = self.send(&msg); + let send_result = self.send(msg); self.buffer.clear(); match send_result { Ok(r) => Ok(r), Err(e) => Err(ClientError::IOError(e)), } } + + fn clear(&mut self) {} } pub struct DummyClient { @@ -233,12 +247,14 @@ impl DummyClient { } impl CCTalkClient for DummyClient { + fn clear(&mut self) {} + fn send_and_check_reply(&mut self, msg: &Message) -> Result { match msg.payload.header { HeaderType::ReadBufferedBillEvents => { let (byte1, byte2) = self.bill_event.to_u8(); - if self.changed == true { + if self.changed { self.counter += 1; self.changed = false; } diff --git a/src/coinacceptor.rs b/src/coinacceptor.rs index 9c5acc7..90dde9e 100644 --- a/src/coinacceptor.rs +++ b/src/coinacceptor.rs @@ -128,13 +128,14 @@ impl CoinAcceptor { let _received = self.client.read_messages(); let received = match _received { Ok(data) => { - log::trace!("Read: {:?}", data); + // log::trace!("Read: {:?}", data); data } Err(error) => { match error { ClientError::CCTalkError(ErrorType::ChecksumError) => { - println!("Checksum error"); + self.client.clear(); + log::error!("Checksum error"); } _ => panic!("Client error: {:?}", error), } @@ -237,12 +238,11 @@ impl CoinAcceptor { self.client.send_message(&msg) } HeaderType::RequestMasterInhibitStatus => { - let status: u8; - if self.cc_master_inhibit { - status = 0u8; + let status: u8 = if self.cc_master_inhibit { + 0u8 } else { - status = 1u8; - } + 1u8 + }; let msg = self.create_message(Payload { header: (HeaderType::Reply), data: (vec![status]), @@ -366,6 +366,8 @@ mod tests { } impl CCTalkClient for MPSCTestClient { + fn clear(&mut self) {} + fn send_and_check_reply(&mut self, _msg: &Message) -> Result { Ok(Payload { header: HeaderType::Unknown(0), diff --git a/src/protocol.rs b/src/protocol.rs index 0a560c8..c70c4c4 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -842,20 +842,18 @@ impl Message { payload: Payload, checksum_type: ChecksumType, ) -> Message { - let length = payload.data.len(); + let length: u8 = payload.data.len() as u8; Message { - destination: destination, - length: length as u8, - source: source, - payload: payload, - checksum_type: checksum_type, + destination, + length, + source, + payload, + checksum_type, } } pub fn encode(&self) -> Vec { - let mut temp = Vec::::new(); - temp.push(self.destination); - temp.push(self.length); + let mut temp = vec![self.destination, self.length]; match self.checksum_type { ChecksumType::SimpleChecksum => { @@ -879,7 +877,7 @@ impl Message { let msg_length = raw.len() as u16; - if msg_length < 2 { + if msg_length < 5 { return Err(ErrorType::PartialMessage); } @@ -899,14 +897,12 @@ impl Message { if Message::validate_checksum(&raw_msg) { checksum_type = ChecksumType::SimpleChecksum; source = raw_msg[2]; + } else if Message::validate_crc(&raw_msg) { + checksum_type = ChecksumType::CRCChecksum; + source = 1; // Source address is always 1 in CRC mode } else { - if Message::validate_crc(&raw_msg) { - checksum_type = ChecksumType::CRCChecksum; - source = 1; // Source address is always 1 in CRC mode - } else { - log::error!("Failed raw: {:?}", raw_msg); - return Err(ErrorType::ChecksumError); - } + log::error!("Failed raw: {:?}", raw_msg); + return Err(ErrorType::ChecksumError); } let mut raw_data = raw_msg.split_off(4); @@ -922,11 +918,11 @@ impl Message { }; Ok(Message { - destination: destination, + destination, length: data_length, - source: source, - payload: payload, - checksum_type: checksum_type, + source, + payload, + checksum_type, }) } @@ -946,16 +942,13 @@ impl Message { } pub fn calc_own_crc(&self) -> CRC { - let mut data = Vec::::new(); - - data.push(self.destination); - data.push(self.length); + let mut data = vec![self.destination, self.length]; data.append(&mut self.payload.encode()); - Message::calc_crc(&data) + Self::calc_crc(&data) } - pub fn calc_crc(data: &Vec) -> CRC { + pub fn calc_crc(data: &[u8]) -> CRC { let poly = 0x1021; let mut crc = 0u16; @@ -973,7 +966,7 @@ impl Message { [(crc & 0xff) as u8, (crc >> 8 & 0xff) as u8] } - pub fn validate_checksum(raw: &Vec) -> bool { + pub fn validate_checksum(raw: &[u8]) -> bool { if raw.is_empty() { log::error!("Validate checksum called on empty message!"); return false; @@ -987,15 +980,15 @@ impl Message { rem == 0 } - pub fn validate_crc(raw: &Vec) -> bool { + pub fn validate_crc(raw: &[u8]) -> bool { if raw.is_empty() { log::error!("Validate CRC called on empty message!"); return false; } - let mut data = raw.clone(); + let mut data = raw.to_owned(); let crc: [u8; 2] = [data.remove(2), data.pop().unwrap()]; - crc == Message::calc_crc(&data) + crc == Self::calc_crc(&data) } }