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

Still working on SerialListener addition.

This commit is contained in:
William Woodall 2012-01-07 15:24:30 -06:00
parent 313b01985a
commit 318bce46bf
11 changed files with 457 additions and 123 deletions

View File

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

View File

@ -1,5 +1,5 @@
ifdef ROS_ROOT ifdef ROS_ROOT
include $(shell rospack find mk)/cmake.mk include $(shell rospack find mk)/cmake.mk
else else
include serial.mk include serial.makefile
endif endif

View File

@ -25,14 +25,14 @@ int main(void) {
SerialListener listener; SerialListener listener;
// Set the time to live for messages to 1 second // Set the time to live for messages to 1 second
listener.setTimeToLive(1000); listener.setTimeToLive(1000);
listener.startListening(serial); listener.startListening(&serial);
listener.listenFor(comparator, callback); listener.listenFor(comparator, callback);
serial.write("?$1E\r"); serial.write("?$1E\r");
if (!listener.listenForOnce("?$1E")) { if (!listener.listenForStringOnce("?$1E")) {
std::cerr << "Didn't get conformation of device version!" << std::endl; std::cerr << "Didn't get conformation of device version!" << std::endl;
return; return 1;
} }
} }

View File

@ -33,6 +33,9 @@
* *
*/ */
#ifndef SERIAL_LISTENER_H
#define SERIAL_LISTENER_H
// Serial // Serial
#include <serial/serial.h> #include <serial/serial.h>
@ -43,14 +46,10 @@
#include <boost/uuid/uuid_generators.hpp> #include <boost/uuid/uuid_generators.hpp>
#include <boost/date_time/posix_time/posix_time.hpp> #include <boost/date_time/posix_time/posix_time.hpp>
#ifndef SERIAL_LISTENER_TEST
#define SERIAL_LISTENER_TEST false
#endif
namespace serial { namespace serial {
/*! /*!
* This is a general function type that is used both as the callback prototype * This is a general function type that is used as the callback prototype
* for asynchronous functions like the default handler callback and the * for asynchronous functions like the default handler callback and the
* listenFor callbacks. * listenFor callbacks.
* *
@ -60,7 +59,7 @@ namespace serial {
* *
* \see SerialListener::listenFor, SerialListener::setDefaultHandler * \see SerialListener::listenFor, SerialListener::setDefaultHandler
*/ */
typedef boost::function<void(const std::string&)> SerialCallback; typedef boost::function<void(const std::string&)> DataCallback;
/*! /*!
* This is a general function type that is used as the comparator callback * This is a general function type that is used as the comparator callback
@ -74,22 +73,101 @@ typedef boost::function<void(const std::string&)> SerialCallback;
*/ */
typedef boost::function<bool(const std::string&)> ComparatorType; typedef boost::function<bool(const std::string&)> ComparatorType;
/*!
* This function type describes the prototype for the logging callbacks.
*
* The function takes a std::string reference and returns nothing. It is
* called from the library when a logging message occurs. This
* allows the library user to hook into this and integrate it with their own
* logging system. It can be set with any of the set<log level>Handler
* functions.
*
* \see SerialListener::setInfoHandler, SerialListener::setDebugHandler,
* SerialListener::setWarningHandler
*/
typedef boost::function<void(const std::string&)> LoggingCallback;
typedef boost::function<void(const std::string&)> InfoCallback; /*!
typedef boost::function<void(const std::string&)> WarningCallback; * This function type describes the prototype for the exception callback.
typedef boost::function<void(const std::string&)> DebugCallback; *
* 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; typedef boost::function<void(const std::exception&)> ExceptionCallback;
typedef boost::uuids::uuid uuid_t; /*!
* This function type describes the prototype for the tokenizer callback.
*
* The function should take a std::string reference and tokenize it into a
* several std::string's and store them in the given
* std::vector<std::string> reference. There are some default ones or the
* user can create their own.
*
* The last element in the std::vector of std::string'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 all complete messages, or:
* "msg1\rpartial_msg2" -> ["msg1","partial_msg2"] for partial messages.
*
* \see SerialListener::setTokenizer, serial::delimeter_tokenizer
*/
typedef boost::function<void(std::string&,std::vector<std::string>&)>
TokenizerType;
class SerialListenerException : public std::exception { /*! This is a convenience alias for boost::uuids::uuid. */
const char * e_what; typedef boost::uuids::uuid uuid_type; // uuid_t is already taken! =(
void
_delimeter_tokenizer (std::string &data, std::vector<std::string> &tokens,
std::string delimeter);
/*!
* 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(delimeter_tokenizer("\r"));
* <\pre>
*
* \see SerialListener::setTokenizer, serial::TokenizerType
*/
class delimeter_tokenizer
{
public: public:
SerialListenerException(const char * e_what) {this->e_what = e_what;} delimeter_tokenizer ();
virtual ~delimeter_tokenizer ();
private:
/* data */
};
TokenizerType
delimeter_tokenizer (std::string delimeter);
/*!
* 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() {std::exception::~exception();}
virtual const char* what() const throw() { virtual const char* what() const throw() {
std::stringstream ss; std::stringstream ss;
ss << "Error listening to serial port: " << this->e_what; ss << "SerialListenerException: " << this->e_what;
return ss.str().c_str(); return ss.str().c_str();
} }
}; };
@ -110,12 +188,36 @@ public:
*/ */
virtual ~SerialListener (); virtual ~SerialListener ();
/***** Configurations ******/
/*! /*!
* Sets the time-to-live (ttl) for messages waiting to be processsed. * Sets the time-to-live (ttl) for messages waiting to be processsed.
* *
* Messages are processed before checking for expiration, therefore they
* will always be passed through filters once before being removed
* due to ttl expiration. The default value for this is 10 ms.
*
* \param ms Time in milliseconds until messages are purged from the buffer. * \param ms Time in milliseconds until messages are purged from the buffer.
*/ */
void setTimeToLive (size_t ms); void setTimeToLive (size_t ms = 10);
/*!
* 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<std::string> 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;
}
/***** Start and Stop Listening ******/
/*! /*!
* Starts a thread to listen for messages and process them through filters. * Starts a thread to listen for messages and process them through filters.
@ -127,9 +229,14 @@ public:
/*! /*!
* Stops the listening thread and blocks until it completely stops. * 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 (); void stopListening ();
/***** Filter Functions ******/
/*! /*!
* Blocks until the given string is detected or until the timeout occurs. * Blocks until the given string is detected or until the timeout occurs.
* *
@ -144,51 +251,198 @@ public:
*/ */
bool listenForStringOnce (std::string token, size_t timeout = 1000); bool listenForStringOnce (std::string token, size_t timeout = 1000);
boost::uuids::uuid listenFor (ComparatorType, SerialCallback); /*!
void stopListeningFor (boost::uuids::uuid filter_uuid); * Setups up a filter that calls a callback when a 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::uuids::uuid a unique identifier used to remove the filter.
*/
uuid_type listenFor (ComparatorType comparator, DataCallback callback);
InfoCallback info; /*!
WarningCallback warn; * Removes a filter by a given uuid.
DebugCallback debug; *
ExceptionCallback handle_exc; * The uuid for a filter is returned by the listenFor function.
SerialCallback default_handler; *
* \param filter_uuid The uuid of the filter to be removed.
*/
void stopListeningFor (uuid_type filter_uuid);
/*!
* Stops listening for anything, but doesn't stop reading the serial port.
*/
void stopListeningForAll ();
/***** 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 info logging message occurs.
*
* This allows you to hook into the message reporting of the library and use
* your own logging facilities.
*
* The provided function must follow this prototype:
* <pre>
* void yourInfoCallback(const std::string &msg)
* </pre>
* Here is an example:
* <pre>
* void yourInfoCallback(const std::string &msg) {
* std::cout << "SerialListener Info: " << msg << std::endl;
* }
* </pre>
* And the resulting call to make it the callback:
* <pre>
* serial::SerialListener listener;
* listener.setInfoCallback(yourInfoCallback);
* </pre>
* Alternatively you can use a class method as a callback using boost::bind:
* <pre>
* #include <boost/bind.hpp>
*
* #include "serial/serial_listener.h"
*
* class MyClass
* {
* public:
* MyClass () {
* listener.setInfoHandler(
* boost::bind(&MyClass::handleInfo, this, _1));
* }
*
* void handleInfo(const std::string &msg) {
* std::cout << "MyClass Info: " << msg << std::endl;
* }
*
* private:
* serial::SerialListener listener;
* };
* </pre>
*
* \param info_handler A function pointer to the callback to handle new
* Info messages.
*
* \see serial::LoggingCallback
*/
void setInfoHandler(LoggingCallback info_handler) {
this->info = info_handler;
}
/*!
* Sets the function to be called when a debug logging message occurs.
*
* This allows you to hook into the message reporting of the library and use
* your own logging facilities.
*
* This works just like SerialListener::setInfoHandler.
*
* \param debug_handler A function pointer to the callback to handle new
* Debug messages.
*
* \see serial::LoggingCallback, SerialListener::setInfoHandler
*/
void setDebugHandler(LoggingCallback debug_handler) {
this->debug = debug_handler;
}
/*!
* Sets the function to be called when a warning logging message occurs.
*
* This allows you to hook into the message reporting of the library and use
* your own logging facilities.
*
* This works just like SerialListener::setInfoHandler.
*
* \param warning_handler A function pointer to the callback to handle new
* Warning messages.
*
* \see serial::LoggingCallback, SerialListener::setInfoHandler
*/
void setWarningHandler(LoggingCallback warning_handler) {
this->warn = warning_handler;
}
private: private:
// Function that loops while listening is true
void listen (); void listen ();
// Called by listen iteratively
std::string listenOnce (std::string data); std::string listenOnce (std::string data);
// Determines how much to read on each loop of listen
size_t determineAmountToRead (); size_t determineAmountToRead ();
// Used in the look for string once function
bool listenForOnceComparator(std::string line); bool listenForOnceComparator(std::string line);
// Tokenizer
TokenizerType tokenize;
// Logging handlers
LoggingCallback warn;
LoggingCallback info;
LoggingCallback debug;
// Exception handler
ExceptionCallback handle_exc;
// Default handler
DataCallback default_handler;
// Persistent listening variables
bool listening; bool listening;
serial::Serial * serial_port; serial::Serial * serial_port;
boost::thread listen_thread; boost::thread listen_thread;
boost::uuids::random_generator random_generator();
std::string buffer; std::string buffer;
std::map<const uuid_t,std::string> lines; std::map<const uuid_type,std::string> lines;
std::map<const uuid_t,boost::posix_time::ptime> ttls; std::map<const uuid_type,boost::posix_time::ptime> ttls;
// For generating random uuids
boost::uuids::random_generator random_generator;
// Setting for ttl on messages
boost::posix_time::time_duration ttl; boost::posix_time::time_duration ttl;
// map<uuid, filter type (blocking/non-blocking)> // map<uuid, filter type (blocking/non-blocking)>
std::map<const uuid_t,std::string> filters; std::map<const uuid_type,std::string> filters;
// map<uuid, comparator> // map<uuid, comparator>
std::map<const uuid_t,ComparatorType> comparators; std::map<const uuid_type,ComparatorType> comparators;
// map<uuid, callback> // map<uuid, callback>
std::map<const uuid_t,SerialCallback> callbacks; std::map<const uuid_type,DataCallback> callbacks;
// map<uuid, conditional_variables> // map<uuid, conditional_variables>
std::map<const uuid_t,boost::condition_variable*> std::map<const uuid_type,boost::condition_variable*> condition_vars;
condition_vars; // Mutex for locking use of filters
// ptime time_start(microsec_clock::local_time());
// //... execution goes here ...
// ptime time_end(microsec_clock::local_time());
// time_duration duration(time_end - time_start);
std::string current_listen_for_one_target;
boost::mutex filter_mux; boost::mutex filter_mux;
// Used as temporary storage for listenForStringOnce
std::string current_listen_for_one_target;
}; };
} }
#endif // SERIAL_LISTENER_H

View File

@ -10,6 +10,11 @@ project(Serial)
## Configurations ## Configurations
# Use clang if available
IF(EXISTS /usr/bin/clang)
set(CMAKE_CXX_COMPILER /usr/bin/clang++)
ENDIF(EXISTS /usr/bin/clang)
option(SERIAL_BUILD_TESTS "Build all of the Serial tests." OFF) option(SERIAL_BUILD_TESTS "Build all of the Serial tests." OFF)
option(SERIAL_BUILD_EXAMPLES "Build all of the Serial examples." OFF) option(SERIAL_BUILD_EXAMPLES "Build all of the Serial examples." OFF)
@ -33,9 +38,9 @@ ENDIF(NOT DEFINED(LIBRARY_OUTPUT_PATH))
include_directories(${PROJECT_SOURCE_DIR}/include) include_directories(${PROJECT_SOURCE_DIR}/include)
# Add default source files # Add default source files
set(SERIAL_SRCS src/serial.cpp) set(SERIAL_SRCS src/serial.cc src/serial_listener.cc)
# Add default header files # Add default header files
set(SERIAL_HEADERS include/serial/serial.h) set(SERIAL_HEADERS include/serial/serial.h include/serial/serial_listener.h)
# Find Boost, if it hasn't already been found # 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) IF(NOT Boost_FOUND OR NOT Boost_SYSTEM_FOUND OR NOT Boost_FILESYSTEM_FOUND OR NOT Boost_THREAD_FOUND)
@ -67,17 +72,33 @@ ENDIF(CMAKE_SYSTEM_NAME MATCHES Darwin)
# If asked to # If asked to
IF(SERIAL_BUILD_EXAMPLES) IF(SERIAL_BUILD_EXAMPLES)
# Compile the Test program # Compile the Serial Test program
add_executable(serial_example examples/serial_example.cpp) add_executable(serial_example examples/serial_example.cc)
# Link the Test program to the Serial library # Link the Test program to the Serial library
target_link_libraries(serial_example serial) target_link_libraries(serial_example serial)
# 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) ENDIF(SERIAL_BUILD_EXAMPLES)
## Build tests ## Build tests
# If asked to # If asked to
IF(SERIAL_BUILD_TESTS) IF(SERIAL_BUILD_TESTS)
# none yet... # 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)
# Link the Test program to the serial library
target_link_libraries(serial_listener_tests ${GTEST_BOTH_LIBRARIES}
serial)
add_test(AllTestsIntest_serial serial_listener_tests)
ENDIF(SERIAL_BUILD_TESTS) ENDIF(SERIAL_BUILD_TESTS)
## Setup install and uninstall ## Setup install and uninstall
@ -95,7 +116,9 @@ IF(NOT SERIAL_DONT_CONFIGURE_INSTALL)
ARCHIVE 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) IF(NOT CMAKE_FIND_INSTALL_PATH)
set(CMAKE_FIND_INSTALL_PATH ${CMAKE_ROOT}) set(CMAKE_FIND_INSTALL_PATH ${CMAKE_ROOT})

View File

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

View File

@ -1,4 +1,4 @@
#include "mdc2250/serial_listener.h" #include "serial/serial_listener.h"
/***** Inline Functions *****/ /***** Inline Functions *****/
@ -22,6 +22,18 @@ inline void defaultExceptionCallback(const std::exception &error) {
using namespace serial; using namespace serial;
void
_delimeter_tokenizer (std::string &data, std::vector<std::string> &tokens,
std::string delimeter)
{
boost::split(tokens, data, boost::is_any_of(delimeter));
}
TokenizerType
delimeter_tokenizer (std::string delimeter) {
return boost::bind(_delimeter_tokenizer, _1, _2, delimeter);
}
/***** Listener Class Functions *****/ /***** Listener Class Functions *****/
SerialListener::SerialListener() : listening(false) { SerialListener::SerialListener() : listening(false) {
@ -32,20 +44,25 @@ SerialListener::SerialListener() : listening(false) {
this->warn = defaultWarningCallback; this->warn = defaultWarningCallback;
this->default_handler = NULL; this->default_handler = NULL;
// Set default tokenizer
this->setTokenizer(delimeter_tokenizer("\r"));
// Set default ttl // Set default ttl
using namespace boost::posix_time; this->setTimeToLive();
this->ttl = time_duration(milliseconds(1000));
} }
SerialListener::~SerialListener() { SerialListener::~SerialListener() {
} }
void SerialListener::setTimeToLive(size_t ms) { void
this->ttl = time_duration(boost::posix_time::milliseconds(ms)); SerialListener::setTimeToLive(size_t ms) {
using namespace boost::posix_time;
this->ttl = time_duration(milliseconds(ms));
} }
void SerialListener::startListening(Serial * serial_port) { void
SerialListener::startListening(Serial * serial_port) {
if (this->listening) { if (this->listening) {
throw(SerialListenerException("Already listening.")); throw(SerialListenerException("Already listening."));
return; return;
@ -61,13 +78,31 @@ void SerialListener::startListening(Serial * serial_port) {
listen_thread = boost::thread(boost::bind(&SerialListener::listen, this)); listen_thread = boost::thread(boost::bind(&SerialListener::listen, this));
} }
void SerialListener::stopListening() { void
SerialListener::stopListening() {
// Stop listening and clear buffers
listening = false; listening = false;
listen_thread.join(); listen_thread.join();
this->buffer = "";
this->lines.clear();
this->ttls.clear();
this->serial_port = NULL; this->serial_port = NULL;
// Delete all the filters
this->stopListeningForAll();
} }
void SerialListener::listen() { void
SerialListener::stopListeningForAll() {
boost::mutex::scoped_lock l(filter_mux);
filters.clear();
comparators.clear();
callbacks.clear();
condition_vars.clear();
}
void
SerialListener::listen() {
// Make sure there is a serial port // Make sure there is a serial port
if (this->serial_port == NULL) { if (this->serial_port == NULL) {
this->handle_exc(SerialListenerException("Invalid serial port.")); this->handle_exc(SerialListenerException("Invalid serial port."));
@ -82,17 +117,20 @@ void SerialListener::listen() {
size_t amount_to_read = determineAmountToRead(); size_t amount_to_read = determineAmountToRead();
// Read some // Read some
std::string temp = this->serial_port->read(amount_to_read); std::string temp = this->serial_port->read(amount_to_read);
if (temp.length() == 0) { // If nothing was read and there is nothing in the lines, then we
// If nothing was read don't interate through the filters // don't need to interate through the filters
if (temp.length() == 0 && lines.size() == 0) {
continue; continue;
} }
// Add the new data to the buffer
this->buffer += temp; this->buffer += temp;
if (this->buffer.find("\r") == std::string::npos) {
// If there is no return carrage in the buffer, then a command hasn't // If there is no return carrage in the buffer, then a command hasn't
// been completed. // been completed and if there is no data in the lines buffer, then
// continue.
if (this->buffer.find("\r") == std::string::npos && lines.size() == 0) {
continue; continue;
} }
// Listen once // Listen once, this parses the buffer and filters the data in lines
buffer = this->listenOnce(buffer); buffer = this->listenOnce(buffer);
// Done parsing lines and buffer should now be set to the left overs // Done parsing lines and buffer should now be set to the left overs
} // while (this->listening) } // while (this->listening)
@ -101,35 +139,40 @@ void SerialListener::listen() {
} }
} }
std::string SerialListener::listenOnce(std::string data) { // TODO: as it is, each line is passed to filters repeatedly until they are
// too old... Change it to only send each line to each filter once and
// then send to new fitlers up until it is too old.
std::string
SerialListener::listenOnce(std::string data) {
std::string left_overs; std::string left_overs;
// TODO: Make the delimeter settable std::vector<uuid_type> to_be_erased;
// Split the buffer by the delimeter // Tokenize the new data
std::vector<std::string> new_lines; std::vector<std::string> new_lines;
boost::split(new_lines, data, boost::is_any_of("\r")); // it only uses \r tokenize(data, new_lines);
// Iterate through new lines and add times to them // Iterate through new lines and add times to them
std::vector<std::string>::iterator it_lines; std::vector<std::string>::iterator it_new;
for(it_lines=new_lines.begin(); it_lines!=new_lines.end(); it_lines++) { for(it_new=new_lines.begin(); it_new != new_lines.end(); it_new++) {
// The last line needs to be put back in the buffer always: // The last line needs to be put back in the buffer always:
// In the case that the string ends with \r the last element will be // In the case that the string ends with \r the last element will be
// empty (""). In the case that it does not the last element will be // empty (""). In the case that it does not the last element will be
// what is left over from the next message that hasn't sent // what is left over from the next message that hasn't sent
// everything. Ex.: "?$1E\r" -> ["?$1E", ""] and // everything. Ex.: "?$1E\r" -> ["?$1E", ""] and
// "?$1E\r$1E=Robo" -> ["?$1E","$1E=Robo"] // "?$1E\r$1E=Robo" -> ["?$1E","$1E=Robo"]
if (it_lines == new_lines.end()-1) { if (it_new == new_lines.end()-1) {
left_overs = (*it_lines); left_overs = (*it_new);
continue; continue;
} }
uuid_t uuid = random_generator(); uuid_type uuid = random_generator();
lines.insert(std::pair<const uuid_t,std::string>(uuid,(*it_lines))); lines.insert(std::pair<const uuid_type,std::string>(uuid,(*it_new)));
using namespace boost::posix_time; using namespace boost::posix_time;
ttls.insert(std::pair<const uuid_t,ptime> ttls.insert(std::pair<const uuid_type,ptime>
(uuid,ptime(microsec_clock::local_time()))); (uuid,ptime(microsec_clock::local_time())));
} }
// Iterate through the lines checking for a match // Iterate through the lines checking for a match
std::map<const uuid_type,std::string>::iterator it_lines;
for(it_lines=lines.begin(); it_lines!=lines.end(); it_lines++) { for(it_lines=lines.begin(); it_lines!=lines.end(); it_lines++) {
std::string line = (*it_lines).second; std::string line = (*it_lines).second;
uuid_t uuid = (*it_lines).first uuid_type uuid = (*it_lines).first;
// If the line is empty, continue // If the line is empty, continue
if (line.length() == 0) { if (line.length() == 0) {
continue; continue;
@ -139,74 +182,80 @@ std::string SerialListener::listenOnce(std::string data) {
// Get the filter lock // Get the filter lock
boost::mutex::scoped_lock l(filter_mux); boost::mutex::scoped_lock l(filter_mux);
// Iterate through each filter // Iterate through each filter
std::map<const uuid_t,std::string>::iterator it; std::map<const uuid_type,std::string>::iterator it;
for(it=filters.begin(); it!=filters.end(); it++) { for(it=filters.begin(); it!=filters.end(); it++) {
if (comparators[(*it).first](line)) { // If comparator matches line if (comparators[(*it).first](line)) { // If comparator matches line
if ((*it).second == "non-blocking") { if ((*it).second == "non-blocking") {
// TODO: Put this callback execution into a queue // TODO: Put this callback execution into a queue. And if I do, make sure to
// keep the line instance around until the callback is done...
// If non-blocking run the callback // If non-blocking run the callback
callbacks[(*it).first](line); callbacks[(*it).first](line);
lines.erase(uuid); to_be_erased.push_back(uuid);
ttls.erase(uuid);
erased = true; erased = true;
} else if ((*it).second == "blocking") { } else if ((*it).second == "blocking") {
// If blocking then notify the waiting call to continue // If blocking then notify the waiting call to continue
condition_vars[(*it).first]->notify_all(); condition_vars[(*it).first]->notify_all();
lines.erase(uuid); to_be_erased.push_back(uuid);
ttls.erase(uuid);
erased = true; erased = true;
} }
matched = true; matched = true;
break; // It matched, continue to next line break; // It matched, continue to next line
} }
} // for(it=filters.begin(); it!=filters.end(); it++) } // for(it=filters.begin(); it!=filters.end(); it++)
// If the comparator doesn't match try another
if (!matched) { // Try to send to default handler
if (this->default_handler) {
this->default_handler(line);
lines.erase(uuid);
ttls.erase(uuid);
erased = true;
}
}
// If not already erased check how old it is, remove the too old // If not already erased check how old it is, remove the too old
if (!erased) { if (!erased) {
using namespace boost::posix_time; using namespace boost::posix_time;
if (ptime(microsec_clock::local_time())-ttls[uuid] > ttl) { if (ptime(microsec_clock::local_time())-ttls[uuid] > ttl) {
lines.erase(uuid); // If there is a default handler pass it on
ttls.erase(uuid); if (this->default_handler) {
// TODO: see above about callback execution queue
this->default_handler(line);
}
to_be_erased.push_back(uuid);
} }
} }
} // for(it_lines=lines.begin(); it_lines!=lines.end(); it_lines++) } // for(it_lines=lines.begin(); it_lines!=lines.end(); it_lines++)
// Remove any lines that need to be erased
// (this must be done outside the iterator to prevent problems incrementing
// the iterator)
std::vector<uuid_type>::iterator it;
for (it=to_be_erased.begin(); it != to_be_erased.end(); it++) {
lines.erase((*it));
ttls.erase((*it));
}
// Return the left_overs
return left_overs; return left_overs;
} }
size_t SerialListener::determineAmountToRead() { size_t
SerialListener::determineAmountToRead() {
// TODO: Make a more intelligent method based on the length of the things // TODO: Make a more intelligent method based on the length of the things
// filters are looking for. i.e.: if the filter is looking for 'V=XX\r' // filters are looking for. i.e.: if the filter is looking for 'V=XX\r'
// make the read amount at least 5. // make the read amount at least 5.
return 5; return 5;
} }
bool SerialListener::listenForOnceComparator(std::string line) { bool
SerialListener::listenForOnceComparator(std::string line) {
if (line == current_listen_for_one_target) if (line == current_listen_for_one_target)
return true; return true;
return false; return false;
} }
bool SerialListener::listenForOnce(std::string token, size_t milliseconds) { bool
SerialListener::listenForStringOnce(std::string token, size_t milliseconds) {
boost::condition_variable cond; boost::condition_variable cond;
boost::mutex mut; boost::mutex mut;
current_listen_for_one_target = token; current_listen_for_one_target = token;
// Create blocking filter // Create blocking filter
uuid_t uuid = random_generator(); uuid_type uuid = random_generator();
std::pair<const uuid_t,std::string> std::pair<const uuid_type,std::string>
filter_pair(uuid, "blocking"); filter_pair(uuid, "blocking");
std::pair<const uuid_t,ComparatorType> std::pair<const uuid_type,ComparatorType>
comparator_pair(uuid, comparator_pair(uuid,
boost::bind(&SerialListener::listenForOnceComparator, this, _1)); boost::bind(&SerialListener::listenForOnceComparator, this, _1));
std::pair<const uuid_t,boost::condition_variable*> std::pair<const uuid_type,boost::condition_variable*>
condition_pair(uuid, &cond); condition_pair(uuid, &cond);
{ {
boost::mutex::scoped_lock l(filter_mux); boost::mutex::scoped_lock l(filter_mux);
@ -233,17 +282,16 @@ bool SerialListener::listenForOnce(std::string token, size_t milliseconds) {
return result; return result;
} }
boost::uuids::uuid uuid_type
SerialListener::listenFor(ComparatorType comparator, SerialListener::listenFor(ComparatorType comparator, DataCallback callback)
SerialCallback callback)
{ {
// Create Filter // Create Filter
uuid_t uuid = random_generator(); uuid_type uuid = random_generator();
std::pair<const uuid_t,std::string> std::pair<const uuid_type,std::string>
filter_pair(uuid, "non-blocking"); filter_pair(uuid, "non-blocking");
std::pair<const uuid_t,ComparatorType> std::pair<const uuid_type,ComparatorType>
comparator_pair(uuid, comparator); comparator_pair(uuid, comparator);
std::pair<const uuid_t,SerialCallback> std::pair<const uuid_type,DataCallback>
callback_pair(uuid, callback); callback_pair(uuid, callback);
{ {
@ -256,7 +304,8 @@ SerialListener::listenFor(ComparatorType comparator,
return uuid; return uuid;
} }
void SerialListener::stopListeningFor(boost::uuids::uuid filter_uuid) { void
SerialListener::stopListeningFor(uuid_type filter_uuid) {
// Delete filter // Delete filter
boost::mutex::scoped_lock l(filter_mux); boost::mutex::scoped_lock l(filter_mux);
filters.erase(filter_uuid); filters.erase(filter_uuid);

Binary file not shown.

View File

@ -2,13 +2,11 @@
#include <boost/bind.hpp> #include <boost/bind.hpp>
#define SERIAL_LISTENER_TEST true
// OMG this is so nasty... // OMG this is so nasty...
#define private public #define private public
#define protected public #define protected public
#include "mdc2250/serial_listener.h" #include "serial/serial_listener.h"
using namespace serial; using namespace serial;
static size_t global_count, global_listen_count; static size_t global_count, global_listen_count;
@ -26,8 +24,8 @@ protected:
listener.default_handler = default_handler; listener.default_handler = default_handler;
} }
void execute_lookForOnce() { void execute_listenForStringOnce() {
listener.listenForOnce("?$1E", 1000); listener.listenForStringOnce("?$1E", 1000);
} }
SerialListener listener; SerialListener listener;
@ -37,6 +35,8 @@ protected:
TEST_F(SerialListenerTests, ignoresEmptyString) { TEST_F(SerialListenerTests, ignoresEmptyString) {
global_count = 0; global_count = 0;
listener.listenOnce("");
boost::this_thread::sleep(boost::posix_time::milliseconds(11));
listener.listenOnce(""); listener.listenOnce("");
ASSERT_TRUE(global_count == 0); ASSERT_TRUE(global_count == 0);
@ -46,6 +46,8 @@ TEST_F(SerialListenerTests, ignoresPartialMessage) {
global_count = 0; global_count = 0;
listener.listenOnce("?$1E\r$1E=Robo"); listener.listenOnce("?$1E\r$1E=Robo");
boost::this_thread::sleep(boost::posix_time::milliseconds(11));
listener.listenOnce("");
ASSERT_EQ(global_count, 1); ASSERT_EQ(global_count, 1);
} }
@ -54,11 +56,13 @@ TEST_F(SerialListenerTests, listenForOnceWorks) {
global_count = 0; global_count = 0;
boost::thread t( boost::thread t(
boost::bind(&SerialListenerTests::execute_lookForOnce, this)); boost::bind(&SerialListenerTests::execute_listenForStringOnce, this));
boost::this_thread::sleep(boost::posix_time::milliseconds(100)); boost::this_thread::sleep(boost::posix_time::milliseconds(100));
listener.listenOnce("\r+\r?$1E\r$1E=Robo"); listener.listenOnce("\r+\r?$1E\r$1E=Robo");
boost::this_thread::sleep(boost::posix_time::milliseconds(11));
listener.listenOnce("");
ASSERT_TRUE(t.timed_join(boost::posix_time::milliseconds(1500))); ASSERT_TRUE(t.timed_join(boost::posix_time::milliseconds(1500)));
@ -74,11 +78,13 @@ TEST_F(SerialListenerTests, listenForOnceTimesout) {
global_count = 0; global_count = 0;
boost::thread t( boost::thread t(
boost::bind(&SerialListenerTests::execute_lookForOnce, this)); boost::bind(&SerialListenerTests::execute_listenForStringOnce, this));
boost::this_thread::sleep(boost::posix_time::milliseconds(100)); boost::this_thread::sleep(boost::posix_time::milliseconds(100));
listener.listenOnce("\r+\r?$1ENOTRIGHT\r$1E=Robo"); listener.listenOnce("\r+\r?$1ENOTRIGHT\r$1E=Robo");
boost::this_thread::sleep(boost::posix_time::milliseconds(11));
listener.listenOnce("");
ASSERT_TRUE(t.timed_join(boost::posix_time::milliseconds(1500))); ASSERT_TRUE(t.timed_join(boost::posix_time::milliseconds(1500)));
@ -104,6 +110,8 @@ TEST_F(SerialListenerTests, listenForWorks) {
listener.listenFor(listenForComparator, listenForCallback); listener.listenFor(listenForComparator, listenForCallback);
listener.listenOnce("\r+\rV=05:06\r?$1E\rV=06:05\r$1E=Robo"); listener.listenOnce("\r+\rV=05:06\r?$1E\rV=06:05\r$1E=Robo");
boost::this_thread::sleep(boost::posix_time::milliseconds(11));
listener.listenOnce("");
ASSERT_EQ(global_count, 2); ASSERT_EQ(global_count, 2);
ASSERT_EQ(global_listen_count, 2); ASSERT_EQ(global_listen_count, 2);