Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand All @@ -19,4 +19,4 @@ name = "cctalk-host"

[dev-dependencies]
env_logger = "0.7"
clap = "2.33.3"
clap = "3.1"
96 changes: 70 additions & 26 deletions examples/cctalk-host/main.rs
Original file line number Diff line number Diff line change
@@ -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...");
}
}
}
15 changes: 8 additions & 7 deletions examples/coinacceptor/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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")
Expand All @@ -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");

Expand Down Expand Up @@ -69,6 +70,6 @@ fn main() {
thread::sleep(Duration::from_millis(20));
}

thread::sleep(Duration::from_millis(100));
thread::sleep(Duration::from_millis(50));
}
}
58 changes: 37 additions & 21 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ impl convert::From<std::io::Error> 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()))
}
}
Expand All @@ -52,6 +52,7 @@ pub trait CCTalkClient {
fn set_bill_event(&mut self, bill_event: BillEvent);
fn read_messages(&mut self) -> Result<Vec<Message>, ClientError>;
fn send_message(&mut self, msg: &Message) -> Result<(), ClientError>;
fn clear(&mut self);
}

pub struct SerialClient {
Expand All @@ -78,15 +79,16 @@ impl SerialClient {
received: &mut Vec<u8>,
messages: &mut Vec<Message>,
) -> Result<(), ClientError> {
// log::debug!("Received: {:?}", received);
self.buffer.append(received);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think by removing this you actually made self.buffer totally useless: it is never written, only read, but it will always be empty. The whole idea of this buffer was to support partial messages. On my experience it was possible to receive partial messages in read_and_decode, that's why we had to save it in a buffer and append the next message to it and do the decoding on the merged message (not just the currently received stuff).
Of course it might be possible that your device is never sending these partial messages, and you have no need for this. But are you sure you have to remove this functionality? Is it causing a problem for you, or you just wanted to simplify things?

Copy link
Contributor Author

@plaes plaes Apr 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this buffer thing was something we really struggled with whenever we connected our Coin Acceptor to the cctalk bus and received the data mid-packet.

Right now we have 30+ coin-acceptor devices working 24/7 and we haven't seen any issues on cctalk side.

@kmuuk Ideas?

// 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 {
Expand Down Expand Up @@ -126,7 +128,6 @@ impl SerialClient {

fn send(&mut self, msg: &Message) -> Result<(), std::io::Error> {
let buf: Vec<u8> = msg.encode();
// log::debug!("Sending CCTalk message: {:?}", msg);
log::trace!("Sending CCTalk message encoded: {:?}", buf);
self.port.write_all(&buf[..])
}
Expand All @@ -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;
}

Expand All @@ -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;
}
}
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -202,17 +214,19 @@ impl CCTalkClient for SerialClient {
fn set_bill_event(&mut self, _: BillEvent) {}

fn read_messages(&mut self) -> Result<Vec<Message>, 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 {
Expand All @@ -233,12 +247,14 @@ impl DummyClient {
}

impl CCTalkClient for DummyClient {
fn clear(&mut self) {}

fn send_and_check_reply(&mut self, msg: &Message) -> Result<Payload, ClientError> {
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;
}
Expand Down
16 changes: 9 additions & 7 deletions src/coinacceptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
Expand Down Expand Up @@ -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]),
Expand Down Expand Up @@ -366,6 +366,8 @@ mod tests {
}

impl CCTalkClient for MPSCTestClient {
fn clear(&mut self) {}

fn send_and_check_reply(&mut self, _msg: &Message) -> Result<Payload, ClientError> {
Ok(Payload {
header: HeaderType::Unknown(0),
Expand Down
Loading