1
0
mirror of https://github.com/wjwwood/serial.git synced 2026-01-22 19:54:57 +08:00

Removing serial listener, next make sure I can compile without boost completely.

This commit is contained in:
William Woodall 2012-02-02 23:35:40 -06:00
parent f7cee5e175
commit 05fa4b8d77
9 changed files with 137 additions and 1657 deletions

View File

@ -1,4 +1,4 @@
find_path(serial_INCLUDE_DIRS serial.h serial_listener.h /usr/include/serial
find_path(serial_INCLUDE_DIRS serial.h /usr/include/serial
/usr/local/include/serial "$ENV{NAMER_ROOT}")
find_library(serial_LIBRARIES serial /usr/lib /usr/local/lib

View File

@ -1,9 +1,3 @@
# # ash_gti's dumb downed makefile so I can more easily test stuff
# CXX=clang++
# CXXFLAGS=-g -I./include -ferror-limit=5 -O3 -Wall -Weffc++ -pedantic -pedantic-errors -Wextra -Wall -Waggregate-return -Wcast-align -Wcast-qual -Wchar-subscripts -Wcomment -Wconversion -Wdisabled-optimization -Wfloat-equal -Wformat -Wformat=2 -Wformat-nonliteral -Wformat-security -Wformat-y2k -Wimplicit -Wimport -Winit-self -Winline -Winvalid-pch -Wlong-long -Wmissing-braces -Wmissing-field-initializers -Wmissing-format-attribute -Wmissing-include-dirs -Wmissing-noreturn -Wpacked -Wparentheses -Wpointer-arith -Wredundant-decls -Wreturn-type -Wsequence-point -Wshadow -Wsign-compare -Wstack-protector -Wstrict-aliasing -Wstrict-aliasing=2 -Wswitch -Wswitch-default -Wswitch-enum -Wtrigraphs -Wuninitialized -Wunknown-pragmas -Wunreachable-code -Wunused -Wunused-function -Wunused-label -Wunused-parameter -Wunused-value -Wunused-variable -Wvariadic-macros -Wvolatile-register-var -Wwrite-strings
#
# test: tests/serial_tests.o src/serial.o src/impl/unix.o
# $(CXX) -o test tests/serial_tests.o src/serial.o src/impl/unix.o
ifdef ROS_ROOT
include $(shell rospack find mk)/cmake.mk
else

View File

@ -1,93 +0,0 @@
#include <iostream>
#include <serial/serial.h>
#include <serial/serial_listener.h>
using namespace serial;
void default_handler(std::string token) {
std::cout << "default_handler got a: " << token << std::endl;
}
void callback(std::string token) {
std::cout << "callback got a: " << token << std::endl;
}
int run() {
// Assuming this device prints the string 'pre-substr-post\r' at 100Hz
Serial serial("/dev/tty.usbserial-A900cfJA", 115200);
SerialListener listener;
listener.startListening(serial);
// Set the tokenizer
// This is the same as the default delimeter, so an explicit call to
// setTokenizer is not necessary if your data is \r delimited.
// You can create your own Tokenizer as well.
listener.setTokenizer(SerialListener::delimeter_tokenizer("\r"));
// Method #1:
// comparator, callback - async
FilterPtr f1 =
listener.createFilter(SerialListener::startsWith("pre"), callback);
SerialListener::sleep(15); // Sleep 15ms, to let the data come in
listener.removeFilter(f1); // Not scoped, must be removed explicity
// Method #2:
// comparator - blocking
{
BlockingFilterPtr f2 =
listener.createBlockingFilter(SerialListener::endsWith("post"));
for (size_t i = 0; i < 3; i++) {
std::string token = f2->wait(100); // Wait for 100 ms or a matched token
if (token != "")
std::cout << "Found something ending with 'post'" << std::endl;
else
std::cout << "Did not find something ending with 'post'" << std::endl;
}
}
// BlockingFilter is scoped and will remove itself, so no removeFilter
// required, but a call like `listener.removeFilter(BlockingFilter) will
// remove it from the filter list so wait will always timeout.
// Method #3:
// comparator, token buffer size - blocking
{
// Give it a comparator, then a buffer size of 10
BufferedFilterPtr f3 =
listener.createBufferedFilter(SerialListener::contains("substr"), 10);
SerialListener::sleep(75); // Sleep 75ms, should have about 7
std::cout << "Caught " << f3->count();
std::cout << " tokens containing 'substr'" << std::endl;
for(size_t i = 0; i < 20; ++i) {
std::string token = f3->wait(5); // Pull message from the buffer
if (token == "") // If an empty string is returned, a timeout occured
break;
}
f3->clear(); // Empties the buffer
if (f3->wait(0) == "") // Non-blocking wait
std::cout << "We won the race condition!" << std::endl;
else
std::cout << "We lost the race condition..." << std::endl;
// The buffer is circular, so the oldest matches will be dropped first
}
// BufferedFilter is scoped and will remove itself just like BlockingFilter.
// Method #4:
// callback - async
// Gets called if a token doesn't match a filter
listener.setDefaultHandler(default_handler);
SerialListener::sleep(25); // Sleep 25 ms, so some default callbacks occur
return 0;
}
int main(void) {
try {
return run();
} catch (std::exception &e) {
std::cerr << e.what() << std::endl;
return 1;
}
}

View File

@ -1,931 +0,0 @@
/*!
* \file serial/serial_listener.h
* \author William Woodall <wjwwood@gmail.com>
* \version 0.1
*
* \section LICENSE
*
* The BSD License
*
* Copyright (c) 2011 William Woodall
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* \section DESCRIPTION
*
* This provides a class that allows for asynchronous serial port reading.
*
*/
#ifndef SERIAL_LISTENER_H
#define SERIAL_LISTENER_H
#ifndef SERIAL_LISTENER_DEBUG
#define SERIAL_LISTENER_DEBUG 0
#endif
// STL
#include <queue>
#include <stdint.h>
#include <iostream>
// Serial
#include <serial/serial.h>
// Boost
#include <boost/function.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/thread.hpp>
#if SERIAL_LISTENER_DEBUG
# warning SerialListener in debug mode
#endif
namespace serial {
/*!
* This is an alias to boost::shared_ptr<const std::string> used for tokens.
*
* This is the type used internally and is the type returned in a vector by
* the tokenizer. The shared_ptr allows for the token to be stored and kept
* around long enough to be used by the comparators and callbacks, but no
* longer. This internal storage is passed as a const std::string reference
* to callbacks, like the DataCallback function type, to prevent implicit
* copying.
*
* \see serial::TokenizerType, serial::SerialListener::setTokenizer
*/
typedef boost::shared_ptr<const std::string> TokenPtr;
/*!
* This is a general function type that is used as the callback prototype
* for asynchronous functions like the default handler callback and the
* listenFor callbacks.
*
* The function takes a std::string reference and returns nothing, it is
* simply passing the resulting line detected by the comparator to the user's
* callback for processing.
*
* \see SerialListener::listenFor, SerialListener::setDefaultHandler
*/
typedef boost::function<void(const std::string&)> DataCallback;
/*!
* This is a general function type that is used as the comparator callback
* prototpe for the listenFor* type functions.
*
* The function takes a std::string reference and returns true if the string
* matches what the comparator is looking for and false if it does not, unless
* otherwise specified.
*
* \see SerialListener::listenFor, SerialListener::listenForOnce
*/
typedef boost::function<bool(const std::string&)> ComparatorType;
/*!
* This function type describes the prototype for the tokenizer callback.
*
* The function should take a std::string reference and tokenize it into a
* several TokenPtr's and store them in the given std::vector<TokenPtr>
* reference. There are some default ones or the user can create their own.
*
* The last element in the std::vector of TokenPtr's should always be
* either an empty string ("") or the last partial message. The last element
* in the std::vector will be put back into the data buffer so that if it is
* incomplete it can be completed when more data is read.
*
* Example: A delimeter tokenizer with a delimeter of "\r". The result would
* be: "msg1\rmsg2\r" -> ["msg1", "msg2", ""] for two complete messages, or:
* "msg1\rpartial_msg2" -> ["msg1","partial_msg2"] for one complete message
* and one partial message.
*
* \see SerialListener::setTokenizer, serial::delimeter_tokenizer,
* serial::TokenPtr
*/
typedef boost::function<void(const std::string&, std::vector<TokenPtr>&)>
TokenizerType;
/*!
* This function type describes the prototype for the exception callback.
*
* The function takes a std::exception reference and returns nothing. It is
* called from the library when an exception occurs in a library thread.
* This exposes these exceptions to the user so they can to error handling.
*
* \see SerialListener::setExceptionHandler
*/
typedef boost::function<void(const std::exception&)> ExceptionCallback;
/*!
* Represents a filter which new data is passed through.
*
* The filter consists of a comparator and a callback. The comparator takes a
* token and returns true if it matches, false if it doesn't. If a match
* occurs the serial listener will dispatch a call of the callback with the
* matched data in a another thread. The comparator should be as short as
* possible, but the callback can be longer since it is executed in a thread
* or thread pool.
*
* \param comparator A ComparatorType that matches incoming data, returns true
* for a match, false othewise.
*
* \param callback A DataCallback that gets called when a match occurs.
*
* \see serial::ComparatorType, serial::DataCallback, serial::FilterPtr
*/
class Filter
{
public:
Filter (ComparatorType comparator, DataCallback callback)
: comparator_(comparator), callback_(callback) {}
virtual ~Filter () {}
ComparatorType comparator_;
DataCallback callback_;
private:
// Disable copy constructors
Filter(const Filter&);
void operator=(const Filter&);
const Filter& operator=(Filter);
};
/*!
* This is an alias to boost::shared_ptr<Filter> used for tokens.
*
* This is used internally and is returned from SerialListener::listenFor like
* functions so that users can later remove those filters by passing the
* FilterPtr.
*
* \see serial::Filter, serial::SerialListener::listenFor,
* serial::SerialListener::listenForOnce
*/
typedef boost::shared_ptr<Filter> FilterPtr;
class BlockingFilter; // Forward declaration
/*!
* Shared Pointer of BlockingFilter, returned by
* SerialListener::createBlockingFilter.
*
* \see serial::BlockingFilter, SerialListener::createBlockingFilter
*/
typedef boost::shared_ptr<BlockingFilter> BlockingFilterPtr;
class BufferedFilter; // Forward declaration
/*!
* Shared Pointer of BufferedFilter, returned by
* SerialListener::createBufferedFilter.
*
* \see serial::BufferedFilter, SerialListener::createBufferedFilter
*/
typedef boost::shared_ptr<BufferedFilter> BufferedFilterPtr;
/*!
* This is a general exception generated by the SerialListener class.
*
* Check the SerialListenerException::what function for the cause.
*
* \param e_what is a std::string that describes the cause of the error.
*/
class SerialListenerException : public std::exception {
const std::string e_what_;
public:
SerialListenerException(const std::string e_what) : e_what_(e_what) {}
~SerialListenerException() throw() {}
virtual const char* what() const throw() {
std::stringstream ss;
ss << "SerialListenerException: " << this->e_what_;
return ss.str().c_str();
}
};
// Based on: http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html
template<typename Data>
class ConcurrentQueue
{
private:
std::queue<Data> the_queue;
mutable boost::mutex the_mutex;
boost::condition_variable the_condition_variable;
public:
void push(Data const& data) {
boost::mutex::scoped_lock lock(the_mutex);
the_queue.push(data);
lock.unlock();
the_condition_variable.notify_one();
}
bool empty() const {
boost::mutex::scoped_lock lock(the_mutex);
return the_queue.empty();
}
bool try_pop(Data& popped_value) {
boost::mutex::scoped_lock lock(the_mutex);
if(the_queue.empty()) {
return false;
}
popped_value=the_queue.front();
the_queue.pop();
return true;
}
bool timed_wait_and_pop(Data& popped_value, long timeout) {
using namespace boost::posix_time;
bool result;
boost::mutex::scoped_lock lock(the_mutex);
result = !the_queue.empty();
if (!result) {
result = the_condition_variable.timed_wait(lock, milliseconds(timeout));
}
if (result) {
popped_value=the_queue.front();
the_queue.pop();
}
return result;
}
void wait_and_pop(Data& popped_value) {
boost::mutex::scoped_lock lock(the_mutex);
while(the_queue.empty()) {
the_condition_variable.wait(lock);
}
popped_value=the_queue.front();
the_queue.pop();
}
size_t size() const {
return the_queue.size();
}
void cancel() {
the_condition_variable.notify_one();
}
void clear() {
boost::mutex::scoped_lock lock(the_mutex);
while (!the_queue.empty()) {
the_queue.pop();
}
}
};
/*!
* Listens to a serial port, facilitates asynchronous reading
*/
class SerialListener
{
public:
/*!
* Creates a new Serial Listener.
*/
SerialListener ();
/*!
* Destructor.
*/
virtual ~SerialListener ();
/***** Configurations ******/
/*!
* Sets the tokenizer to be used when tokenizing the data into tokens.
*
* This function is given a std::string of data and is responsible for
* tokenizing that data into a std::vector<TokenPtr> of data tokens.
* The default tokenizer splits the data by the ascii return carriage.
* The user can create their own tokenizer or use one of the default ones.
*
* \param tokenizer Function for tokenizing the incoming data.
*
* \see serial::TokenizerType, serial::delimeter_tokenizer
*/
void
setTokenizer (TokenizerType tokenizer) {
this->tokenize = tokenizer;
}
/*!
* Sets the number of bytes to be read at a time by the listener.
*
* \param chunk_size Number of bytes to be read at a time.
*/
void
setChunkSize (size_t chunk_size) {
this->chunk_size_ = chunk_size;
}
/***** Start and Stop Listening ******/
/*!
* Starts a thread to listen for messages and process them through filters.
*
* \param serial_port Pointer to a serial::Serial object that is used to
* retrieve new data.
*/
void
startListening (serial::Serial &serial_port);
/*!
* Stops the listening thread and blocks until it completely stops.
*
* This function also clears all of the active filters from listenFor and
* similar functions.
*/
void
stopListening ();
/***** Filter Functions ******/
/*!
* Creates a filter that calls a callback when the comparator returns true.
*
* The user provides a comparator and a callback, and every time a line is
* received the comparator is called and the comparator has to evaluate the
* line and return true if it matches and false if it doesn't. If it does
* match, the callback is called with the resulting line.
*
* \param comparator This is a comparator for detecting if a line matches.
* The comparartor receives a std::string reference and must return a true
* if it matches and false if it doesn't.
*
* \param callback This is the handler for when a match occurs. It is given
* a std::string reference of the line that matched your comparator.
*
* \return boost::shared_ptr<Filter> so you can remove it later.
*
* \see SerialListener::removeFilter
*/
FilterPtr
createFilter (ComparatorType comparator, DataCallback callback);
/*!
* Creates a BlockingFilter which blocks until the comparator returns true.
*
* The user provides a comparator, and every time a line is
* received the comparator is called and the comparator has to evaluate the
* line and return true if it matches and false if it doesn't. If it does
* match, any threads that have called BlockingFilter::wait will be
* notified. The BlockingFilter will remove itself when its destructor is
* called, i.e. when it leaves the scope, so in those cases an explicit call
* to SerialListener::removeFilter is not needed.
*
* \param comparator This is a comparator for detecting if a line matches.
* The comparartor receives a std::string reference and must return a true
* if it matches and false if it doesn't.
*
* \return BlockingFilterPtr So you can call BlockingFilter::wait on it.
*
* \see SerialListener::removeFilter, serial::BlockingFilter,
* serial::BlockingFilterPtr
*/
BlockingFilterPtr
createBlockingFilter (ComparatorType comparator);
/*!
* Creates a BlockingFilter blocks until the comparator returns true.
*
* The user provides a comparator, and every time a line is
* received the comparator is called and the comparator has to evaluate the
* line and return true if it matches and false if it doesn't. If it does
* match, any threads that have called BlockingFilter::wait will be
* notified. The BlockingFilter will remove itself when its destructor is
* called, i.e. when it leaves the scope, so in those cases an explicit call
* to SerialListener::removeFilter is not needed.
*
* \param comparator This is a comparator for detecting if a line matches.
* The comparartor receives a std::string reference and must return a true
* if it matches and false if it doesn't.
*
* \param buffer_size This is the number of tokens to be buffered by the
* BufferedFilter, defaults to 1024.
*
* \return BlockingFilter So you can call BlockingFilter::wait on it.
*
* \see SerialListener::removeFilter, serial::BufferedFilter,
* serial::BufferedFilterPtr
*/
BufferedFilterPtr
createBufferedFilter (ComparatorType comparator, size_t buffer_size = 1024);
/*!
* Removes a filter by a given FilterPtr.
*
* \param filter_ptr A shared pointer to the filter to be removed.
*
* \see SerialListener::createFilter
*/
void
removeFilter (FilterPtr filter_ptr);
/*!
* Removes a BlockingFilter.
*
* The BlockingFilter will remove itself if the destructor is called.
*
* \param blocking_filter A BlockingFilter to be removed.
*
* \see SerialListener::createBlockingFilter
*/
void
removeFilter (BlockingFilterPtr blocking_filter);
/*!
* Removes a BufferedFilter.
*
* The BufferedFilter will remove itself if the destructor is called.
*
* \param buffered_filter A BufferedFilter to be removed.
*
* \see SerialListener::createBufferedFilter
*/
void
removeFilter (BufferedFilterPtr buffered_filter);
/*!
* Removes all filters.
*/
void
removeAllFilters ();
/***** Hooks and Handlers ******/
/*!
* Sets the handler to be called when a lines is not caught by a filter.
*
* This allows you to set a catch all function that will get called
* everytime a line is not matched by a filter and the ttl expires.
*
* Setting the callbacks works just like SerialListener::setInfoHandler.
*
* \param default_handler A function pointer to the callback to handle
* unmatched and expired messages.
*
* \see serial::DataCallback, SerialListener::setInfoHandler
*/
void setDefaultHandler(DataCallback default_handler) {
this->_default_handler = default_handler;
}
/*!
* Sets the function to be called when an exception occurs internally.
*
* This allows you to hook into the exceptions that occur in threads inside
* the serial listener library.
*
* \param exception_handler A function pointer to the callback to handle new
* interal exceptions.
*
* \see serial::ExceptionCallback
*/
void
setExceptionHandler (ExceptionCallback exception_handler) {
this->handle_exc = exception_handler;
}
/***** Static Functions ******/
/*!
* Sleeps for a given number of milliseconds.
*
* \param milliseconds number of milliseconds to sleep.
*/
static void
sleep (long milliseconds) {
boost::int64_t ms(milliseconds);
boost::this_thread::sleep(boost::posix_time::milliseconds(ms));
}
/*!
* This returns a tokenizer that splits on a given delimeter.
*
* The delimeter is passed into the function and a TokenizerType is returned
* that can be passed to SerialListener::setTokenizer.
*
* Example:
* <pre>
* my_listener.setTokenizer(SerialListener::delimeter_tokenizer("\r"));
* <\pre>
*
* \param delimeter A std::string that is used as a delimeter when
* tokenizing data.
*
* \return TokenizerType A tokenizer function type that can be passed to
* SerialListener::setTokenizer.
*
* \see SerialListener::setTokenizer, serial::TokenizerType
*/
static TokenizerType
delimeter_tokenizer (std::string delimeter) {
return boost::bind(&SerialListener::_delimeter_tokenizer,
_1, _2, delimeter);
}
/*!
* This returns a comparator that matches only the exact string given.
*
* This can be used with listenFor or listenForOnce:
*
* Example:
* <pre>
* my_listener.listenFor(SerialListener::exactly("my_string"),
* my_callback);
* <\pre>
*
* \param exact_str A std::string that is used as the exact string to match
* when comparing tokens for matching.
*
* \return ComparatorType A comparator function type that can be passed to
* SerialListener::listenFor or SerialListener::listenForOnce.
*
* \see SerialListener::listenFor, SerialListener::listenForOnce,
* serial::ComparatorType
*/
static ComparatorType
exactly (std::string exact_str) {
return boost::bind(&SerialListener::_exactly, _1, exact_str);
}
/*!
* This returns a comparator that looks for a given prefix.
*
* This can be used with listenFor or listenForOnce:
*
* Example:
* <pre>
* my_listener.listenFor(SerialListener::startsWith("V="), my_callback);
* <\pre>
*
* \param prefix A std::string that is used as the prefix string to match
* when comparing tokens for matching.
*
* \return ComparatorType A comparator function type that can be passed to
* SerialListener::listenFor or SerialListener::listenForOnce.
*
* \see SerialListener::listenFor, SerialListener::listenForOnce,
* serial::ComparatorType
*/
static ComparatorType
startsWith (std::string prefix) {
return boost::bind(&SerialListener::_startsWith, _1, prefix);
}
/*!
* This returns a comparator that looks for a given postfix.
*
* This can be used with listenFor or listenForOnce:
*
* Example:
* <pre>
* my_listener.listenFor(SerialListener::endsWith(";"), my_callback);
* <\pre>
*
* \param postfix A std::string that is used as the postfix string to match
* when comparing tokens for matching.
*
* \return ComparatorType A comparator function type that can be passed to
* SerialListener::listenFor or SerialListener::listenForOnce.
*
* \see SerialListener::listenFor, SerialListener::listenForOnce,
* serial::ComparatorType
*/
static ComparatorType
endsWith (std::string postfix) {
return boost::bind(&SerialListener::_endsWith, _1, postfix);
}
/*!
* This returns a comparator that looks for a given substring in the token.
*
* This can be used with listenFor or listenForOnce:
*
* Example:
* <pre>
* my_listener.listenFor(SerialListener::contains("some string"),
* my_callback);
* <\pre>
*
* \param substr A std::string that is used as the search substring to match
* when comparing tokens for matching.
*
* \return ComparatorType A comparator function type that can be passed to
* SerialListener::listenFor or SerialListener::listenForOnce.
*
* \see SerialListener::listenFor, SerialListener::listenForOnce,
* serial::ComparatorType
*/
static ComparatorType
contains (std::string substr) {
return boost::bind(_contains, _1, substr);
}
private:
// Disable copy constructors
SerialListener(const SerialListener&);
void operator=(const SerialListener&);
const SerialListener& operator=(SerialListener);
// delimeter tokenizer function
static void
_delimeter_tokenizer (const std::string &data,
std::vector<TokenPtr> &tokens,
std::string delimeter)
{
typedef std::vector<std::string> find_vector_type;
find_vector_type t;
boost::split(t, data, boost::is_any_of(delimeter));
for (find_vector_type::iterator it = t.begin(); it != t.end(); it++)
tokens.push_back(TokenPtr( new std::string(*it) ));
}
// exact comparator function
static bool
_exactly (const std::string& token, std::string exact_str) {
#if SERIAL_LISTENER_DEBUG
std::cerr << "In exactly callback(" << token.length() << "): ";
std::cerr << token << " == " << exact_str << ": ";
if (token == exact_str)
std::cerr << "True";
else
std::cerr << "False";
std::cerr << std::endl;
#endif
return token == exact_str;
}
// startswith comparator function
static bool
_startsWith (const std::string& token, std::string prefix) {
#if SERIAL_LISTENER_DEBUG
std::cerr << "In startsWith callback(" << token.length() << "): ";
std::cerr << token << " starts with " << prefix;
std::cerr << "?: ";
if (token.substr(0,prefix.length()) == prefix)
std::cerr << "True";
else
std::cerr << "False";
std::cerr << std::endl;
#endif
return token.substr(0,prefix.length()) == prefix;
}
// endswith comparator function
static bool
_endsWith (const std::string& token, std::string postfix) {
#if SERIAL_LISTENER_DEBUG
std::cerr << "In endsWith callback(";
std::cerr << token.length();
std::cerr << "): " << token;
std::cerr << " ends with " << postfix << "?: ";
if (token.substr(token.length()-postfix.length()) == postfix)
std::cerr << "True";
else
std::cerr << "False";
std::cerr << std::endl;
#endif
return token.substr(token.length()-postfix.length()) == postfix;
}
// contains comparator function
static bool
_contains (const std::string& token, std::string substr) {
return token.find(substr) != std::string::npos;
}
// Gets some data from the serial port
void readSomeData (std::string &temp, size_t this_many) {
// Make sure there is a serial port
if (this->serial_port_ == NULL) {
this->handle_exc(SerialListenerException("Invalid serial port."));
}
// Make sure the serial port is open
if (!this->serial_port_->isOpen()) {
this->handle_exc(SerialListenerException("Serial port not open."));
}
temp = this->serial_port_->read(this_many);
}
// Runs the new_tokens through all the filters
void filter (std::vector<TokenPtr> &tokens);
// Function that loops while listening is true
void listen ();
// Target of callback thread
void callback ();
// Determines how much to read on each loop of listen
size_t determineAmountToRead ();
// Tokenizer
TokenizerType tokenize;
// Exception handler
ExceptionCallback handle_exc;
// Default handler
FilterPtr default_filter;
DataCallback _default_handler;
ComparatorType default_comparator;
void default_handler(const std::string &token);
// Persistent listening variables
bool listening;
char serial_port_padding[7];
serial::Serial * serial_port_;
boost::thread listen_thread;
std::string data_buffer;
size_t chunk_size_;
// Callback related variables
// filter id, token
// filter id == 0 is going to be default handled
ConcurrentQueue<std::pair<FilterPtr,TokenPtr> >
callback_queue;
boost::thread callback_thread;
// Mutex for locking use of filters
boost::mutex filter_mux;
// vector of filter ids
std::vector<FilterPtr> filters;
};
/*!
* This is the a filter that provides a wait function for blocking until a
* match is found.
*
* This should probably not be created manually, but instead should be
* constructed using SerialListener::createBlockingFilter(ComparatorType)
* function which returns a BlockingFilter instance.
*
* \see serial::SerialListener::ComparatorType,
* serial::SerialListener::createBlockingFilter
*/
class BlockingFilter
{
public:
BlockingFilter (ComparatorType comparator, SerialListener &listener) {
this->listener_ = &listener;
DataCallback cb = boost::bind(&BlockingFilter::callback, this, _1);
this->filter_ptr = this->listener_->createFilter(comparator, cb);
}
virtual ~BlockingFilter () {
this->listener_->removeFilter(filter_ptr);
this->result = "";
this->cond.notify_all();
}
/*!
* Waits a given number of milliseconds or until a token is matched. If a
* token is matched it is returned, otherwise an empty string is returned.
*
* \param ms Time in milliseconds to wait on a new token.
*
* \return std::string token that was matched or "" if none were matched.
*/
std::string wait(long ms) {
this->result = "";
boost::unique_lock<boost::mutex> lock(this->mutex);
this->cond.timed_wait(lock, boost::posix_time::milliseconds(ms));
return this->result;
}
FilterPtr filter_ptr;
void callback(const std::string& token) {
#if SERIAL_LISTENER_DEBUG
std::cerr << "In BlockingFilter callback(" << token.length() << "): ";
std::cerr << token << std::endl;
#endif
this->cond.notify_all();
this->result = token;
}
private:
SerialListener * listener_;
boost::condition_variable cond;
boost::mutex mutex;
std::string result;
};
/*!
* This is the a filter that provides a wait function for blocking until a
* match is found. It will also buffer up to a given buffer size of tokens so
* that they can be counted or accessed after they are matched by the filter.
*
* This should probably not be created manually, but instead should be
* constructed using SerialListener::createBufferedFilter(ComparatorType)
* function which returns a BufferedFilter instance.
*
* The internal buffer is a circular queue buffer, so when the buffer is full,
* the oldest token is dropped and the new one is added. Additionally, when
* wait is a called the oldest available token is returned.
*
* \see serial::SerialListener::ComparatorType,
* serial::SerialListener::createBufferedFilter
*/
class BufferedFilter
{
public:
BufferedFilter (ComparatorType comparator, size_t buffer_size,
SerialListener &listener)
: buffer_size_(buffer_size)
{
this->listener_ = &listener;
DataCallback cb = boost::bind(&BufferedFilter::callback, this, _1);
this->filter_ptr = this->listener_->createFilter(comparator, cb);
}
virtual ~BufferedFilter () {
this->listener_->removeFilter(filter_ptr);
this->queue.clear();
this->result = "";
}
/*!
* Waits a given number of milliseconds or until a matched token is
* available in the buffer. If a token is matched it is returned, otherwise
* an empty string is returned.
*
* \param ms Time in milliseconds to wait on a new token. If ms is set to 0
* then it will try to get a new token if one is available but will not
* block.
*
* \return std::string token that was matched or "" if none were matched.
*/
std::string wait(long ms) {
if (ms == 0) {
if (!this->queue.try_pop(this->result)) {
this->result = "";
}
} else {
if (!this->queue.timed_wait_and_pop(this->result, ms)) {
this->result = "";
}
}
return result;
}
/*!
* Clears the buffer of any tokens.
*/
void clear() {
queue.clear();
}
/*!
* Returns the number of tokens waiting in the buffer.
*/
size_t count() {
return queue.size();
}
/*!
* Returns the capacity of the buffer.
*/
size_t capacity() {
return buffer_size_;
}
FilterPtr filter_ptr;
void callback(const std::string &token) {
#if SERIAL_LISTENER_DEBUG
std::cerr << "In BufferedFilter callback(" << token.length() << "): ";
std::cerr << token << std::endl;
#endif
std::string throw_away;
if (this->queue.size() == this->buffer_size_) {
this->queue.wait_and_pop(throw_away);
}
this->queue.push(token);
}
private:
size_t buffer_size_;
SerialListener * listener_;
ConcurrentQueue<std::string> queue;
std::string result;
};
} // namespace serial
#endif // SERIAL_LISTENER_H

View File

@ -1,158 +1,133 @@
macro(build_serial)
## Project Setup
cmake_minimum_required(VERSION 2.4.6)
if(COMMAND cmake_policy)
cmake_policy(SET CMP0003 NEW)
endif(COMMAND cmake_policy)
## Project Setup
cmake_minimum_required(VERSION 2.4.6)
project(Serial)
if(COMMAND cmake_policy)
cmake_policy(SET CMP0003 NEW)
endif(COMMAND cmake_policy)
## Configurations
project(Serial)
# Use clang if available
IF(EXISTS /usr/bin/clang)
set(CMAKE_CXX_COMPILER /usr/bin/clang++)
set(CMAKE_OSX_DEPLOYMENT_TARGET "")
# set(CMAKE_CXX_FLAGS "-ferror-limit=5 -std=c++0x -stdlib=libc++")
set(CMAKE_CXX_FLAGS "-ferror-limit=5 -Wall -Weffc++ -pedantic -pedantic-errors -Wextra -Wall -Waggregate-return -Wcast-align -Wcast-qual -Wchar-subscripts -Wcomment -Wconversion -Wdisabled-optimization -Wfloat-equal -Wformat -Wformat=2 -Wformat-nonliteral -Wformat-security -Wformat-y2k -Wimplicit -Wimport -Winit-self -Winline -Winvalid-pch -Wlong-long -Wmissing-braces -Wmissing-field-initializers -Wmissing-format-attribute -Wmissing-include-dirs -Wmissing-noreturn -Wpacked -Wparentheses -Wpointer-arith -Wredundant-decls -Wreturn-type -Wsequence-point -Wshadow -Wsign-compare -Wstack-protector -Wstrict-aliasing -Wstrict-aliasing=2 -Wswitch -Wswitch-default -Wswitch-enum -Wtrigraphs -Wuninitialized -Wunknown-pragmas -Wunreachable-code -Wunused -Wunused-function -Wunused-label -Wunused-parameter -Wunused-value -Wunused-variable -Wvariadic-macros -Wvolatile-register-var -Wwrite-strings")
set(CMAKE_BUILD_TYPE Debug)
ENDIF(EXISTS /usr/bin/clang)
## Configurations
option(SERIAL_BUILD_TESTS "Build all of the Serial tests." OFF)
option(SERIAL_BUILD_EXAMPLES "Build all of the Serial examples." OFF)
# Use clang if available
IF(EXISTS /usr/bin/clang)
set(CMAKE_CXX_COMPILER /usr/bin/clang++)
set(CMAKE_OSX_DEPLOYMENT_TARGET "")
set(SERIAL_BUILD_WARNINGS TRUE)
IF(SERIAL_BUILD_WARNINGS)
set(CMAKE_CXX_FLAGS "-ferror-limit=5 -Wall -Weffc++ -pedantic -pedantic-errors -Wextra -Wall -Waggregate-return -Wcast-align -Wcast-qual -Wchar-subscripts -Wcomment -Wconversion -Wdisabled-optimization -Wfloat-equal -Wformat -Wformat=2 -Wformat-nonliteral -Wformat-security -Wformat-y2k -Wimplicit -Wimport -Winit-self -Winline -Winvalid-pch -Wlong-long -Wmissing-braces -Wmissing-field-initializers -Wmissing-format-attribute -Wmissing-include-dirs -Wmissing-noreturn -Wpacked -Wparentheses -Wpointer-arith -Wredundant-decls -Wreturn-type -Wsequence-point -Wshadow -Wsign-compare -Wstack-protector -Wstrict-aliasing -Wstrict-aliasing=2 -Wswitch -Wswitch-default -Wswitch-enum -Wtrigraphs -Wuninitialized -Wunknown-pragmas -Wunreachable-code -Wunused -Wunused-function -Wunused-label -Wunused-parameter -Wunused-value -Wunused-variable -Wvariadic-macros -Wvolatile-register-var -Wwrite-strings")
ELSE(SERIAL_BUILD_WARNINGS)
set(CMAKE_CXX_FLAGS "-ferror-limit=5")
ENDIF(SERIAL_BUILD_WARNINGS)
set(CMAKE_BUILD_TYPE Debug)
ENDIF(EXISTS /usr/bin/clang)
# Allow for building shared libs override
IF(NOT BUILD_SHARED_LIBS)
set(BUILD_SHARED_LIBS OFF)
ENDIF(NOT BUILD_SHARED_LIBS)
option(SERIAL_BUILD_TESTS "Build all of the Serial tests." OFF)
option(SERIAL_BUILD_EXAMPLES "Build all of the Serial examples." OFF)
# Set the default path for built executables to the "bin" directory
IF(NOT DEFINED(EXECUTABLE_OUTPUT_PATH))
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
ENDIF(NOT DEFINED(EXECUTABLE_OUTPUT_PATH))
# set the default path for built libraries to the "lib" directory
IF(NOT DEFINED(LIBRARY_OUTPUT_PATH))
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
ENDIF(NOT DEFINED(LIBRARY_OUTPUT_PATH))
# Allow for building shared libs override
IF(NOT BUILD_SHARED_LIBS)
set(BUILD_SHARED_LIBS OFF)
ENDIF(NOT BUILD_SHARED_LIBS)
## Configure the build system
# Set the default path for built executables to the "bin" directory
IF(NOT DEFINED(EXECUTABLE_OUTPUT_PATH))
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
ENDIF(NOT DEFINED(EXECUTABLE_OUTPUT_PATH))
# set the default path for built libraries to the "lib" directory
IF(NOT DEFINED(LIBRARY_OUTPUT_PATH))
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
ENDIF(NOT DEFINED(LIBRARY_OUTPUT_PATH))
# Add the include folder to the include path
include_directories(${PROJECT_SOURCE_DIR}/include)
## Configure the build system
# Add default source files
set(SERIAL_SRCS src/serial.cc src/impl/unix.cc src/serial_listener.cc)
# Add default header files
set(SERIAL_HEADERS include/serial/serial.h include/serial/serial_listener.h)
# Add the include folder to the include path
include_directories(${PROJECT_SOURCE_DIR}/include)
IF(UNIX)
list(APPEND SERIAL_SRCS src/impl/unix.cc)
list(APPEND SERIAL_HEADERS include/serial/impl/unix.h)
ELSE(UNIX)
ENDIF(UNIX)
# Add default source files
set(SERIAL_SRCS src/serial.cc)
IF(WIN32)
list(APPEND SERIAL_SRCS src/impl/windows.cc)
ELSE(WIN32)
list(APPEND SERIAL_SRCS src/impl/unix.cc)
ENDIF(WIN32)
# Add default header files
set(SERIAL_HEADERS include/serial/serial.h)
# Find Boost, if it hasn't already been found
IF(NOT Boost_FOUND OR NOT Boost_SYSTEM_FOUND OR NOT Boost_FILESYSTEM_FOUND OR NOT Boost_THREAD_FOUND)
find_package(Boost COMPONENTS system filesystem thread REQUIRED)
ENDIF(NOT Boost_FOUND OR NOT Boost_SYSTEM_FOUND OR NOT Boost_FILESYSTEM_FOUND OR NOT Boost_THREAD_FOUND)
## Build the Serial Library
link_directories(${Boost_LIBRARY_DIRS})
include_directories(${Boost_INCLUDE_DIRS})
# Compile the Library
add_library(serial ${SERIAL_SRCS})
set(SERIAL_LINK_LIBS ${Boost_SYSTEM_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY})
## Build Examples
## Build the Serial Library
# If asked to
IF(SERIAL_BUILD_EXAMPLES)
# Compile the Serial Test program
add_executable(serial_example examples/serial_example.cc)
# Link the Test program to the Serial library
target_link_libraries(serial_example serial)
ENDIF(SERIAL_BUILD_EXAMPLES)
# Compile the Library
add_library(serial ${SERIAL_SRCS} ${SERIAL_HEADERS})
target_link_libraries(serial ${SERIAL_LINK_LIBS})
IF( WIN32 )
target_link_libraries(serial wsock32)
ENDIF( )
## Build tests
## Build Examples
# If asked to
IF(SERIAL_BUILD_TESTS)
# Find Google Test
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})
# If asked to
IF(SERIAL_BUILD_EXAMPLES)
# Compile the Serial Test program
add_executable(serial_example examples/serial_example.cc)
# Link the Test program to the Serial library
target_link_libraries(serial_example serial)
# Compile the Serial Test program
add_executable(serial_tests tests/serial_tests.cc)
# Link the Test program to the serial library
target_link_libraries(serial_tests ${GTEST_BOTH_LIBRARIES}
serial)
add_test(AllTestsIntest_serial serial_tests)
ENDIF(SERIAL_BUILD_TESTS)
## Setup install and uninstall
# Unless asked not to...
IF(NOT SERIAL_DONT_CONFIGURE_INSTALL)
# Configure make install
IF(NOT CMAKE_INSTALL_PREFIX)
SET(CMAKE_INSTALL_PREFIX /usr/local)
ENDIF(NOT CMAKE_INSTALL_PREFIX)
# Compile the Serial Listener Test program
add_executable(serial_listener_example
examples/serial_listener_example.cc)
# Link the Test program to the Serial library
target_link_libraries(serial_listener_example serial)
ENDIF(SERIAL_BUILD_EXAMPLES)
## Build tests
# If asked to
IF(SERIAL_BUILD_TESTS)
# Find Google Test
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})
# Compile the Serial Listener Test program
add_executable(serial_listener_tests tests/serial_listener_tests.cc)
add_executable(serial_tests tests/serial_tests.cc)
# Link the Test program to the serial library
target_link_libraries(serial_listener_tests ${GTEST_BOTH_LIBRARIES}
serial)
target_link_libraries(serial_tests ${GTEST_BOTH_LIBRARIES}
serial)
# # See: http://code.google.com/p/googlemock/issues/detail?id=146
# add_definitions(-DGTEST_USE_OWN_TR1_TUPLE=1)
add_test(AllTestsIntest_serial serial_listener_tests)
add_test(AllTestsIntest_serial serial_tests)
ENDIF(SERIAL_BUILD_TESTS)
## Setup install and uninstall
# Unless asked not to...
IF(NOT SERIAL_DONT_CONFIGURE_INSTALL)
# Configure make install
IF(NOT CMAKE_INSTALL_PREFIX)
SET(CMAKE_INSTALL_PREFIX /usr/local)
ENDIF(NOT CMAKE_INSTALL_PREFIX)
INSTALL(TARGETS serial
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
INSTALL(TARGETS serial
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
INSTALL(FILES include/serial/serial.h
DESTINATION include/serial)
INSTALL(FILES include/serial/serial.h
include/serial/serial_listener.h
DESTINATION include/serial)
IF(NOT CMAKE_FIND_INSTALL_PATH)
set(CMAKE_FIND_INSTALL_PATH ${CMAKE_ROOT})
ENDIF(NOT CMAKE_FIND_INSTALL_PATH)
IF(NOT CMAKE_FIND_INSTALL_PATH)
set(CMAKE_FIND_INSTALL_PATH ${CMAKE_ROOT})
ENDIF(NOT CMAKE_FIND_INSTALL_PATH)
INSTALL(FILES Findserial.cmake
DESTINATION ${CMAKE_FIND_INSTALL_PATH}/Modules/)
INSTALL(FILES Findserial.cmake DESTINATION ${CMAKE_FIND_INSTALL_PATH}/Modules/)
ADD_CUSTOM_TARGET(uninstall @echo uninstall package)
ADD_CUSTOM_TARGET(uninstall @echo uninstall package)
IF (UNIX)
ADD_CUSTOM_COMMAND(
COMMENT "uninstall package"
COMMAND xargs ARGS rm < install_manifest.txt
IF (UNIX)
ADD_CUSTOM_COMMAND(
COMMENT "uninstall package"
COMMAND xargs ARGS rm < install_manifest.txt
TARGET uninstall
)
ELSE(UNIX)
ADD_CUSTOM_COMMAND(
COMMENT "uninstall only implemented in unix"
TARGET uninstall
)
ENDIF(UNIX)
ENDIF(NOT SERIAL_DONT_CONFIGURE_INSTALL)
TARGET uninstall
)
ELSE(UNIX)
ADD_CUSTOM_COMMAND(
COMMENT "uninstall only implemented in unix"
TARGET uninstall
)
ENDIF(UNIX)
ENDIF(NOT SERIAL_DONT_CONFIGURE_INSTALL)
endmacro(build_serial)

View File

@ -37,4 +37,4 @@ ifneq ($(MAKE),)
else
cd build && make
endif
cd bin && ./serial_listener_tests
cd bin && ./serial_tests

View File

@ -1,51 +1,41 @@
macro(build_serial)
cmake_minimum_required(VERSION 2.4.6)
include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake)
# Set the build type. Options are:
# Coverage : w/ debug symbols, w/o optimization, w/ code-coverage
# Debug : w/ debug symbols, w/o optimization
# Release : w/o debug symbols, w/ optimization
# RelWithDebInfo : w/ debug symbols, w/ optimization
# MinSizeRel : w/o debug symbols, w/ optimization, stripped binaries
set(ROS_BUILD_TYPE RelWithDebInfo)
cmake_minimum_required(VERSION 2.4.6)
include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake)
rosbuild_init()
# Set the build type. Options are:
# Coverage : w/ debug symbols, w/o optimization, w/ code-coverage
# Debug : w/ debug symbols, w/o optimization
# Release : w/o debug symbols, w/ optimization
# RelWithDebInfo : w/ debug symbols, w/ optimization
# MinSizeRel : w/o debug symbols, w/ optimization, stripped binaries
set(ROS_BUILD_TYPE RelWithDebInfo)
#set the default path for built executables to the "bin" directory
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#set the default path for built libraries to the "lib" directory
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
rosbuild_init()
include_directories(include)
#set the default path for built executables to the "bin" directory
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#set the default path for built libraries to the "lib" directory
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
set(SERIAL_SRCS src/serial.cc)
if(UNIX)
list(APPEND SERIAL_SRCS src/impl/unix.cc)
else(UNIX)
list(APPEND SERIAL_SRCS src/impl/windows.cc)
endif(UNIX)
list(APPEND SERIAL_SRCS src/serial_listener.cc)
include_directories(include)
# Build the serial library
rosbuild_add_library(${PROJECT_NAME} ${SERIAL_SRCS})
set(SERIAL_SRCS src/serial.cc)
if(UNIX)
list(APPEND SERIAL_SRCS src/impl/unix.cc)
else(UNIX)
list(APPEND SERIAL_SRCS src/impl/winows.cc)
endif(UNIX)
# Add boost dependencies
rosbuild_add_boost_directories()
rosbuild_link_boost(${PROJECT_NAME} system filesystem thread)
# Build the serial library
rosbuild_add_library(${PROJECT_NAME} ${SERIAL_SRCS})
# Build example
rosbuild_add_executable(serial_example examples/serial_example.cc)
target_link_libraries(serial_example ${PROJECT_NAME})
# Build example
rosbuild_add_executable(serial_example examples/serial_example.cc)
target_link_libraries(serial_example ${PROJECT_NAME})
rosbuild_add_executable(serial_listener_example
examples/serial_listener_example.cc)
target_link_libraries(serial_listener_example ${PROJECT_NAME})
# Create unit tests
rosbuild_add_gtest(serial_tests tests/serial_tests.cc)
target_link_libraries(serial_tests ${PROJECT_NAME})
rosbuild_add_gtest(serial_listener_tests tests/serial_listener_tests.cc)
target_link_libraries(serial_listener_tests ${PROJECT_NAME})
# Create unit tests
rosbuild_add_gtest(serial_tests tests/serial_tests.cc)
target_link_libraries(serial_tests ${PROJECT_NAME})
endmacro(build_serial)

View File

@ -1,217 +0,0 @@
/* Copyright 2012 William Woodall and John Harrison */
#include "serial/serial_listener.h"
/***** Inline Functions *****/
inline void defaultExceptionCallback(const std::exception &error) {
std::cerr << "SerialListener Unhandled Exception: " << error.what();
std::cerr << std::endl;
}
inline bool defaultComparator(const std::string &token) {
return token == token;
}
using namespace serial;
/***** Listener Class Functions *****/
void
SerialListener::default_handler(const std::string &token) {
if (this->_default_handler)
this->_default_handler(token);
}
SerialListener::SerialListener() : listening(false), chunk_size_(5) {
// Set default callbacks
this->handle_exc = defaultExceptionCallback;
// Default handler stuff
this->_default_handler = NULL;
this->default_comparator = defaultComparator;
DataCallback tmp = boost::bind(&SerialListener::default_handler, this, _1);
this->default_filter = FilterPtr(new Filter(default_comparator, tmp));
// Set default tokenizer
this->setTokenizer(delimeter_tokenizer("\r"));
}
SerialListener::~SerialListener() {
if (this->listening) {
this->stopListening();
}
}
void
SerialListener::callback() {
try {
// <filter id, token>
std::pair<FilterPtr,TokenPtr> pair;
while (this->listening) {
if (this->callback_queue.timed_wait_and_pop(pair, 10)) {
if (this->listening) {
try {
if (pair.first != NULL && pair.second != NULL) {
pair.first->callback_((*pair.second));
}
} catch (std::exception &e) {
this->handle_exc(e);
}// try callback
} // if listening
} // if popped
} // while (this->listening)
} catch (std::exception &e) {
this->handle_exc(SerialListenerException(e.what()));
}
}
void
SerialListener::startListening(Serial &serial_port) {
if (this->listening) {
throw(SerialListenerException("Already listening."));
return;
}
this->listening = true;
this->serial_port_ = &serial_port;
if (!this->serial_port_->isOpen()) {
throw(SerialListenerException("Serial port not open."));
return;
}
listen_thread = boost::thread(boost::bind(&SerialListener::listen, this));
// Start the callback thread
callback_thread =
boost::thread(boost::bind(&SerialListener::callback, this));
}
void
SerialListener::stopListening() {
// Stop listening and clear buffers
listening = false;
listen_thread.join();
callback_thread.join();
this->data_buffer = "";
this->serial_port_ = NULL;
}
size_t
SerialListener::determineAmountToRead() {
// TODO: Make a more intelligent method based on the length of the things
// filters are looking for. e.g.: if the filter is looking for 'V=XX\r'
// make the read amount at least 5.
return this->chunk_size_;
}
void
SerialListener::filter(std::vector<TokenPtr> &tokens) {
// Lock the filters while filtering
boost::mutex::scoped_lock lock(filter_mux);
// Iterate through each new token and filter them
std::vector<TokenPtr>::iterator it;
for (it=tokens.begin(); it!=tokens.end(); it++) {
TokenPtr token = (*it);
// If it is empty then pass it
if (token->empty()) {
continue;
}
bool matched = false;
// Iterate through each filter
std::vector<FilterPtr>::iterator itt;
for (itt=filters.begin(); itt!=filters.end(); itt++) {
FilterPtr filter = (*itt);
if (filter->comparator_((*token))) {
callback_queue.push(std::make_pair(filter,token));
matched = true;
break;
}
} // for (itt=filters.begin(); itt!=filters.end(); itt++)
// If matched is false then send it to the default handler
if (!matched) {
callback_queue.push(std::make_pair(default_filter,token));
}
} // for (it=tokens.begin(); it!=tokens.end(); it++)
}
void
SerialListener::listen() {
try {
while (this->listening) {
// Read some data
std::string temp;
this->readSomeData(temp, determineAmountToRead());
// If nothing was read then we
// don't need to iterate through the filters
if (temp.length() != 0) {
// Add the new data to the buffer
this->data_buffer += temp;
// Call the tokenizer on the updated buffer
std::vector<TokenPtr> new_tokens;
this->tokenize(this->data_buffer, new_tokens);
// Put the last token back in the data buffer
this->data_buffer = (*new_tokens.back());
new_tokens.pop_back();
// Run the new tokens through existing filters
this->filter(new_tokens);
}
// Done parsing lines and buffer should now be set to the left overs
} // while (this->listening)
} catch (std::exception &e) {
this->handle_exc(SerialListenerException(e.what()));
}
}
/***** Filter Functions *****/
FilterPtr
SerialListener::createFilter(ComparatorType comparator, DataCallback callback)
{
FilterPtr filter_ptr(new Filter(comparator, callback));
boost::mutex::scoped_lock l(filter_mux);
this->filters.push_back(filter_ptr);
return filter_ptr;
}
BlockingFilterPtr
SerialListener::createBlockingFilter(ComparatorType comparator) {
return BlockingFilterPtr(
new BlockingFilter(comparator, (*this)));
}
BufferedFilterPtr
SerialListener::createBufferedFilter(ComparatorType comparator,
size_t buffer_size)
{
return BufferedFilterPtr(
new BufferedFilter(comparator, buffer_size, (*this)));
}
void
SerialListener::removeFilter(FilterPtr filter_ptr) {
boost::mutex::scoped_lock l(filter_mux);
filters.erase(std::find(filters.begin(),filters.end(),filter_ptr));
}
void
SerialListener::removeFilter(BlockingFilterPtr blocking_filter) {
this->removeFilter(blocking_filter->filter_ptr);
}
void
SerialListener::removeFilter(BufferedFilterPtr buffered_filter) {
this->removeFilter(buffered_filter->filter_ptr);
}
void
SerialListener::removeAllFilters() {
boost::mutex::scoped_lock l(filter_mux);
filters.clear();
callback_queue.clear();
}

View File

@ -1,238 +0,0 @@
/* To run these tests you need to change the define below to the serial port
* with a loop back device attached.
*
* Alternatively you could use an Arduino:
*
* void setup()
* {
* Serial.begin(115200);
* }
*
* void loop()
* {
* while (Serial.available() > 0) {
* Serial.write(Serial.read());
* }
* }
*
*/
// #define SERIAL_PORT_NAME "/dev/tty.usbserial-A900cfJA"
#define SERIAL_PORT_NAME "p0"
#include "gtest/gtest.h"
#include <boost/bind.hpp>
// OMG this is so nasty...
#define private public
#define protected public
#include "serial/serial_listener.h"
using namespace serial;
static size_t global_count, global_listen_count;
static bool matched;
void filter_handler(std::string token) {
global_listen_count++;
std::cout << "filter_handler got: " << token << std::endl;
}
void default_handler(std::string line) {
global_count++;
std::cout << "default_handler got: " << line << std::endl;
}
namespace {
void my_sleep(long milliseconds) {
boost::this_thread::sleep(boost::posix_time::milliseconds(milliseconds));
}
class SerialListenerTests : public ::testing::Test {
protected:
virtual void SetUp() {
port1 = new Serial("/dev/pty"SERIAL_PORT_NAME, 115200, 10);
port2 = new Serial("/dev/tty"SERIAL_PORT_NAME, 115200, 250);
listener.setDefaultHandler(default_handler);
listener.startListening((*port1));
}
virtual void TearDown() {
listener.stopListening();
delete port1;
delete port2;
}
SerialListener listener;
Serial * port1;
Serial * port2;
};
TEST_F(SerialListenerTests, handlesPartialMessage) {
global_count = 0;
std::string input_str = "?$1E\r$1E=Robo";
std::cout << "writing: ?$1E<cr>$1E=Robo" << std::endl;
port2->write(input_str);
// Allow time for processing
my_sleep(50);
ASSERT_EQ(1, global_count);
input_str = "?$1E\r$1E=Roboteq\r";
std::cout << "writing: ?$1E<cr>$1E=Roboteq<cr>" << std::endl;
port2->write(input_str);
// Allow time for processing
my_sleep(50);
ASSERT_EQ(3, global_count);
}
TEST_F(SerialListenerTests, normalFilterWorks) {
global_count = 0;
global_listen_count = 0;
std::string input_str = "?$1E\r$1E=Robo\rV=1334:1337\rT=123";
// Setup filter
FilterPtr filt_1 =
listener.createFilter(SerialListener::startsWith("V="), filter_handler);
std::cout << "writing: ?$1E<cr>$1E=Robo<cr>V=1334:1337<cr>T=123";
std::cout << std::endl;
port2->write(input_str);
// Allow time for processing
my_sleep(50);
ASSERT_EQ(2, global_count);
ASSERT_EQ(1, global_listen_count);
}
void run_blocking_filter(BlockingFilterPtr filt_1) {
// Wait 100 ms for a match
std::string temp = filt_1->wait(100);
if (temp.empty()) {
return;
}
std::cout << "blocking filter matched: " << temp << std::endl;
global_listen_count++;
matched = true;
}
TEST_F(SerialListenerTests, blockingFilterWorks) {
global_count = 0;
global_listen_count = 0;
matched = false;
std::string input_str = "?$1E\r$1E=Robo\rV=1334:1337\rT=123";
// Setup blocking filter
BlockingFilterPtr filt_1 =
listener.createBlockingFilter(SerialListener::startsWith("$1E="));
boost::thread t(boost::bind(run_blocking_filter, filt_1));
std::cout << "writing: ?$1E<cr>$1E=Robo<cr>V=1334:1337<cr>T=123";
std::cout << std::endl;
port2->write(input_str);
// Allow time for processing
my_sleep(50);
using boost::posix_time::milliseconds;
ASSERT_TRUE(t.timed_join(milliseconds(10)));
ASSERT_EQ(2, global_count);
ASSERT_EQ(1, global_listen_count);
ASSERT_TRUE(matched);
}
TEST_F(SerialListenerTests, blockingFilterTimesOut) {
global_count = 0;
global_listen_count = 0;
matched = false;
std::string input_str = "?$1E\r$1E=Robo\rV=1334:1337\rT=123";
// Setup blocking filter
BlockingFilterPtr filt_1 =
listener.createBlockingFilter(SerialListener::startsWith("T="));
boost::thread t(boost::bind(run_blocking_filter, filt_1));
std::cout << "writing: ?$1E<cr>$1E=Robo<cr>V=1334:1337<cr>T=123";
std::cout << std::endl;
port2->write(input_str);
// Allow time for processing
my_sleep(50);
using boost::posix_time::milliseconds;
// First one should not be within timeout, should be false
ASSERT_FALSE(t.timed_join(milliseconds(10)));
// Second one should capture timeout and return true to join
ASSERT_TRUE(t.timed_join(milliseconds(60)));
ASSERT_EQ(3, global_count);
ASSERT_EQ(0, global_listen_count);
ASSERT_FALSE(matched);
}
void write_later(Serial *port, std::string input_str, long wait_for) {
my_sleep(wait_for);
port->write(input_str);
}
TEST_F(SerialListenerTests, bufferedFilterWorks) {
global_count = 0;
std::string input_str = "?$1E\r+\r$1E=Robo\rV=1334:1337\rT=123";
// Setup buffered filter, buffer size 3
BufferedFilterPtr filt_1 =
listener.createBufferedFilter(SerialListener::exactly("+"), 3);
// Write the string to the port 10 ms in the future
boost::thread t(boost::bind(write_later, port2, input_str, 10));
// This should be empty because of a timeout
ASSERT_TRUE(filt_1->wait(2).empty());
// Make sure wait works properly
ASSERT_EQ("+", filt_1->wait(20));
// This should be empty cause there was only one
ASSERT_TRUE(filt_1->wait(2).empty());
// The queue in the filter should be empty
ASSERT_EQ(0, filt_1->queue.size());
ASSERT_EQ(3, global_count);
t.join();
}
TEST_F(SerialListenerTests, bufferedFilterQueueWorks) {
global_count = 0;
std::string input_str = "?$1E$\r+\r$1E=Robo$\rV=1334:1337$\rT=123$\r";
// Setup buffered filter, buffer size 3
BufferedFilterPtr filt_1 =
listener.createBufferedFilter(SerialListener::endsWith("$"), 3);
// write the string
port2->write(input_str);
my_sleep(20); // Let things process
// There should have been four matches
// therefore the first one should the second match.
ASSERT_EQ("$1E=Robo$", filt_1->wait(1));
ASSERT_EQ("V=1334:1337$", filt_1->wait(1));
ASSERT_EQ("T=123$", filt_1->wait(1));
ASSERT_EQ(0, filt_1->queue.size());
ASSERT_EQ(1, global_count);
}
} // namespace
int main(int argc, char **argv) {
try {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
} catch (std::exception &e) {
std::cerr << "Unhandled Exception: " << e.what() << std::endl;
}
return 1;
}