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:
parent
313b01985a
commit
318bce46bf
@ -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}")
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@ -1,5 +1,5 @@
|
||||
ifdef ROS_ROOT
|
||||
include $(shell rospack find mk)/cmake.mk
|
||||
else
|
||||
include serial.mk
|
||||
include serial.makefile
|
||||
endif
|
||||
|
||||
@ -25,14 +25,14 @@ int main(void) {
|
||||
SerialListener listener;
|
||||
// Set the time to live for messages to 1 second
|
||||
listener.setTimeToLive(1000);
|
||||
listener.startListening(serial);
|
||||
listener.startListening(&serial);
|
||||
|
||||
listener.listenFor(comparator, callback);
|
||||
|
||||
serial.write("?$1E\r");
|
||||
if (!listener.listenForOnce("?$1E")) {
|
||||
if (!listener.listenForStringOnce("?$1E")) {
|
||||
std::cerr << "Didn't get conformation of device version!" << std::endl;
|
||||
return;
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -33,6 +33,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SERIAL_LISTENER_H
|
||||
#define SERIAL_LISTENER_H
|
||||
|
||||
// Serial
|
||||
#include <serial/serial.h>
|
||||
|
||||
@ -43,14 +46,10 @@
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#ifndef SERIAL_LISTENER_TEST
|
||||
#define SERIAL_LISTENER_TEST false
|
||||
#endif
|
||||
|
||||
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
|
||||
* listenFor callbacks.
|
||||
*
|
||||
@ -60,7 +59,7 @@ namespace serial {
|
||||
*
|
||||
* \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
|
||||
@ -74,22 +73,101 @@ typedef boost::function<void(const std::string&)> SerialCallback;
|
||||
*/
|
||||
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;
|
||||
typedef boost::function<void(const std::string&)> DebugCallback;
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
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 {
|
||||
const char * e_what;
|
||||
/*! This is a convenience alias for boost::uuids::uuid. */
|
||||
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:
|
||||
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() {
|
||||
std::stringstream ss;
|
||||
ss << "Error listening to serial port: " << this->e_what;
|
||||
ss << "SerialListenerException: " << this->e_what;
|
||||
return ss.str().c_str();
|
||||
}
|
||||
};
|
||||
@ -110,12 +188,36 @@ public:
|
||||
*/
|
||||
virtual ~SerialListener ();
|
||||
|
||||
/***** Configurations ******/
|
||||
|
||||
/*!
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
@ -127,9 +229,14 @@ public:
|
||||
|
||||
/*!
|
||||
* 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 ******/
|
||||
|
||||
/*!
|
||||
* 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);
|
||||
|
||||
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;
|
||||
DebugCallback debug;
|
||||
ExceptionCallback handle_exc;
|
||||
SerialCallback default_handler;
|
||||
/*!
|
||||
* Removes a filter by a given uuid.
|
||||
*
|
||||
* The uuid for a filter is returned by the listenFor function.
|
||||
*
|
||||
* \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:
|
||||
void listen();
|
||||
std::string listenOnce(std::string data);
|
||||
size_t determineAmountToRead();
|
||||
// Function that loops while listening is true
|
||||
void listen ();
|
||||
// Called by listen iteratively
|
||||
std::string listenOnce (std::string data);
|
||||
// Determines how much to read on each loop of listen
|
||||
size_t determineAmountToRead ();
|
||||
// Used in the look for string once function
|
||||
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;
|
||||
|
||||
serial::Serial * serial_port;
|
||||
|
||||
boost::thread listen_thread;
|
||||
boost::uuids::random_generator random_generator();
|
||||
|
||||
std::string buffer;
|
||||
std::map<const uuid_t,std::string> lines;
|
||||
std::map<const uuid_t,boost::posix_time::ptime> ttls;
|
||||
std::map<const uuid_type,std::string> lines;
|
||||
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;
|
||||
|
||||
// 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>
|
||||
std::map<const uuid_t,ComparatorType> comparators;
|
||||
std::map<const uuid_type,ComparatorType> comparators;
|
||||
// map<uuid, callback>
|
||||
std::map<const uuid_t,SerialCallback> callbacks;
|
||||
std::map<const uuid_type,DataCallback> callbacks;
|
||||
// map<uuid, conditional_variables>
|
||||
std::map<const uuid_t,boost::condition_variable*>
|
||||
condition_vars;
|
||||
|
||||
// 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;
|
||||
|
||||
std::map<const uuid_type,boost::condition_variable*> condition_vars;
|
||||
// Mutex for locking use of filters
|
||||
boost::mutex filter_mux;
|
||||
|
||||
// Used as temporary storage for listenForStringOnce
|
||||
std::string current_listen_for_one_target;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SERIAL_LISTENER_H
|
||||
35
serial.cmake
35
serial.cmake
@ -10,6 +10,11 @@ project(Serial)
|
||||
|
||||
## 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_EXAMPLES "Build all of the Serial examples." OFF)
|
||||
|
||||
@ -33,9 +38,9 @@ ENDIF(NOT DEFINED(LIBRARY_OUTPUT_PATH))
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
|
||||
# Add default source files
|
||||
set(SERIAL_SRCS src/serial.cpp)
|
||||
set(SERIAL_SRCS src/serial.cc src/serial_listener.cc)
|
||||
# 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
|
||||
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(SERIAL_BUILD_EXAMPLES)
|
||||
# Compile the Test program
|
||||
add_executable(serial_example examples/serial_example.cpp)
|
||||
# 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 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)
|
||||
# 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)
|
||||
|
||||
## Setup install and uninstall
|
||||
@ -95,7 +116,9 @@ IF(NOT SERIAL_DONT_CONFIGURE_INSTALL)
|
||||
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})
|
||||
|
||||
@ -30,4 +30,4 @@ ifneq ($(MAKE),)
|
||||
else
|
||||
cd build && make
|
||||
endif
|
||||
# cd bin && ./serial_tests
|
||||
cd bin && ./serial_listener_tests
|
||||
@ -1,4 +1,4 @@
|
||||
#include "mdc2250/serial_listener.h"
|
||||
#include "serial/serial_listener.h"
|
||||
|
||||
/***** Inline Functions *****/
|
||||
|
||||
@ -22,6 +22,18 @@ inline void defaultExceptionCallback(const std::exception &error) {
|
||||
|
||||
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 *****/
|
||||
|
||||
SerialListener::SerialListener() : listening(false) {
|
||||
@ -32,20 +44,25 @@ SerialListener::SerialListener() : listening(false) {
|
||||
this->warn = defaultWarningCallback;
|
||||
this->default_handler = NULL;
|
||||
|
||||
// Set default tokenizer
|
||||
this->setTokenizer(delimeter_tokenizer("\r"));
|
||||
|
||||
// Set default ttl
|
||||
using namespace boost::posix_time;
|
||||
this->ttl = time_duration(milliseconds(1000));
|
||||
this->setTimeToLive();
|
||||
}
|
||||
|
||||
SerialListener::~SerialListener() {
|
||||
|
||||
}
|
||||
|
||||
void SerialListener::setTimeToLive(size_t ms) {
|
||||
this->ttl = time_duration(boost::posix_time::milliseconds(ms));
|
||||
void
|
||||
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) {
|
||||
throw(SerialListenerException("Already listening."));
|
||||
return;
|
||||
@ -61,13 +78,31 @@ void SerialListener::startListening(Serial * serial_port) {
|
||||
listen_thread = boost::thread(boost::bind(&SerialListener::listen, this));
|
||||
}
|
||||
|
||||
void SerialListener::stopListening() {
|
||||
void
|
||||
SerialListener::stopListening() {
|
||||
// Stop listening and clear buffers
|
||||
listening = false;
|
||||
listen_thread.join();
|
||||
this->buffer = "";
|
||||
this->lines.clear();
|
||||
this->ttls.clear();
|
||||
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
|
||||
if (this->serial_port == NULL) {
|
||||
this->handle_exc(SerialListenerException("Invalid serial port."));
|
||||
@ -82,17 +117,20 @@ void SerialListener::listen() {
|
||||
size_t amount_to_read = determineAmountToRead();
|
||||
// Read some
|
||||
std::string temp = this->serial_port->read(amount_to_read);
|
||||
if (temp.length() == 0) {
|
||||
// If nothing was read don't interate through the filters
|
||||
// If nothing was read and there is nothing in the lines, then we
|
||||
// don't need to interate through the filters
|
||||
if (temp.length() == 0 && lines.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
// Add the new data to the buffer
|
||||
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
|
||||
// been completed.
|
||||
// If there is no return carrage in the buffer, then a command hasn't
|
||||
// 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;
|
||||
}
|
||||
// Listen once
|
||||
// Listen once, this parses the buffer and filters the data in lines
|
||||
buffer = this->listenOnce(buffer);
|
||||
// Done parsing lines and buffer should now be set to the left overs
|
||||
} // 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;
|
||||
// TODO: Make the delimeter settable
|
||||
// Split the buffer by the delimeter
|
||||
std::vector<uuid_type> to_be_erased;
|
||||
// Tokenize the new data
|
||||
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
|
||||
std::vector<std::string>::iterator it_lines;
|
||||
for(it_lines=new_lines.begin(); it_lines!=new_lines.end(); it_lines++) {
|
||||
std::vector<std::string>::iterator it_new;
|
||||
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:
|
||||
// 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
|
||||
// what is left over from the next message that hasn't sent
|
||||
// everything. Ex.: "?$1E\r" -> ["?$1E", ""] and
|
||||
// "?$1E\r$1E=Robo" -> ["?$1E","$1E=Robo"]
|
||||
if (it_lines == new_lines.end()-1) {
|
||||
left_overs = (*it_lines);
|
||||
if (it_new == new_lines.end()-1) {
|
||||
left_overs = (*it_new);
|
||||
continue;
|
||||
}
|
||||
uuid_t uuid = random_generator();
|
||||
lines.insert(std::pair<const uuid_t,std::string>(uuid,(*it_lines)));
|
||||
uuid_type uuid = random_generator();
|
||||
lines.insert(std::pair<const uuid_type,std::string>(uuid,(*it_new)));
|
||||
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())));
|
||||
}
|
||||
// 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++) {
|
||||
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 (line.length() == 0) {
|
||||
continue;
|
||||
@ -139,74 +182,80 @@ std::string SerialListener::listenOnce(std::string data) {
|
||||
// Get the filter lock
|
||||
boost::mutex::scoped_lock l(filter_mux);
|
||||
// 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++) {
|
||||
if (comparators[(*it).first](line)) { // If comparator matches line
|
||||
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
|
||||
callbacks[(*it).first](line);
|
||||
lines.erase(uuid);
|
||||
ttls.erase(uuid);
|
||||
to_be_erased.push_back(uuid);
|
||||
erased = true;
|
||||
} else if ((*it).second == "blocking") {
|
||||
// If blocking then notify the waiting call to continue
|
||||
condition_vars[(*it).first]->notify_all();
|
||||
lines.erase(uuid);
|
||||
ttls.erase(uuid);
|
||||
to_be_erased.push_back(uuid);
|
||||
erased = true;
|
||||
}
|
||||
matched = true;
|
||||
break; // It matched, continue to next line
|
||||
}
|
||||
} // 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 (!erased) {
|
||||
using namespace boost::posix_time;
|
||||
if (ptime(microsec_clock::local_time())-ttls[uuid] > ttl) {
|
||||
lines.erase(uuid);
|
||||
ttls.erase(uuid);
|
||||
// If there is a default handler pass it on
|
||||
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++)
|
||||
// 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;
|
||||
}
|
||||
|
||||
size_t SerialListener::determineAmountToRead() {
|
||||
size_t
|
||||
SerialListener::determineAmountToRead() {
|
||||
// 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'
|
||||
// make the read amount at least 5.
|
||||
return 5;
|
||||
}
|
||||
|
||||
bool SerialListener::listenForOnceComparator(std::string line) {
|
||||
bool
|
||||
SerialListener::listenForOnceComparator(std::string line) {
|
||||
if (line == current_listen_for_one_target)
|
||||
return true;
|
||||
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::mutex mut;
|
||||
current_listen_for_one_target = token;
|
||||
|
||||
// Create blocking filter
|
||||
uuid_t uuid = random_generator();
|
||||
std::pair<const uuid_t,std::string>
|
||||
uuid_type uuid = random_generator();
|
||||
std::pair<const uuid_type,std::string>
|
||||
filter_pair(uuid, "blocking");
|
||||
std::pair<const uuid_t,ComparatorType>
|
||||
std::pair<const uuid_type,ComparatorType>
|
||||
comparator_pair(uuid,
|
||||
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);
|
||||
{
|
||||
boost::mutex::scoped_lock l(filter_mux);
|
||||
@ -233,17 +282,16 @@ bool SerialListener::listenForOnce(std::string token, size_t milliseconds) {
|
||||
return result;
|
||||
}
|
||||
|
||||
boost::uuids::uuid
|
||||
SerialListener::listenFor(ComparatorType comparator,
|
||||
SerialCallback callback)
|
||||
uuid_type
|
||||
SerialListener::listenFor(ComparatorType comparator, DataCallback callback)
|
||||
{
|
||||
// Create Filter
|
||||
uuid_t uuid = random_generator();
|
||||
std::pair<const uuid_t,std::string>
|
||||
uuid_type uuid = random_generator();
|
||||
std::pair<const uuid_type,std::string>
|
||||
filter_pair(uuid, "non-blocking");
|
||||
std::pair<const uuid_t,ComparatorType>
|
||||
std::pair<const uuid_type,ComparatorType>
|
||||
comparator_pair(uuid, comparator);
|
||||
std::pair<const uuid_t,SerialCallback>
|
||||
std::pair<const uuid_type,DataCallback>
|
||||
callback_pair(uuid, callback);
|
||||
|
||||
{
|
||||
@ -256,7 +304,8 @@ SerialListener::listenFor(ComparatorType comparator,
|
||||
return uuid;
|
||||
}
|
||||
|
||||
void SerialListener::stopListeningFor(boost::uuids::uuid filter_uuid) {
|
||||
void
|
||||
SerialListener::stopListeningFor(uuid_type filter_uuid) {
|
||||
// Delete filter
|
||||
boost::mutex::scoped_lock l(filter_mux);
|
||||
filters.erase(filter_uuid);
|
||||
|
||||
BIN
tests/proof_of_concepts/tokenizer.cc.out
Executable file
BIN
tests/proof_of_concepts/tokenizer.cc.out
Executable file
Binary file not shown.
@ -2,13 +2,11 @@
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#define SERIAL_LISTENER_TEST true
|
||||
|
||||
// OMG this is so nasty...
|
||||
#define private public
|
||||
#define protected public
|
||||
|
||||
#include "mdc2250/serial_listener.h"
|
||||
#include "serial/serial_listener.h"
|
||||
using namespace serial;
|
||||
|
||||
static size_t global_count, global_listen_count;
|
||||
@ -26,8 +24,8 @@ protected:
|
||||
listener.default_handler = default_handler;
|
||||
}
|
||||
|
||||
void execute_lookForOnce() {
|
||||
listener.listenForOnce("?$1E", 1000);
|
||||
void execute_listenForStringOnce() {
|
||||
listener.listenForStringOnce("?$1E", 1000);
|
||||
}
|
||||
|
||||
SerialListener listener;
|
||||
@ -37,6 +35,8 @@ protected:
|
||||
TEST_F(SerialListenerTests, ignoresEmptyString) {
|
||||
global_count = 0;
|
||||
|
||||
listener.listenOnce("");
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(11));
|
||||
listener.listenOnce("");
|
||||
|
||||
ASSERT_TRUE(global_count == 0);
|
||||
@ -46,6 +46,8 @@ TEST_F(SerialListenerTests, ignoresPartialMessage) {
|
||||
global_count = 0;
|
||||
|
||||
listener.listenOnce("?$1E\r$1E=Robo");
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(11));
|
||||
listener.listenOnce("");
|
||||
|
||||
ASSERT_EQ(global_count, 1);
|
||||
}
|
||||
@ -54,11 +56,13 @@ TEST_F(SerialListenerTests, listenForOnceWorks) {
|
||||
global_count = 0;
|
||||
|
||||
boost::thread t(
|
||||
boost::bind(&SerialListenerTests::execute_lookForOnce, this));
|
||||
boost::bind(&SerialListenerTests::execute_listenForStringOnce, this));
|
||||
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
|
||||
|
||||
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)));
|
||||
|
||||
@ -74,11 +78,13 @@ TEST_F(SerialListenerTests, listenForOnceTimesout) {
|
||||
global_count = 0;
|
||||
|
||||
boost::thread t(
|
||||
boost::bind(&SerialListenerTests::execute_lookForOnce, this));
|
||||
boost::bind(&SerialListenerTests::execute_listenForStringOnce, this));
|
||||
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
|
||||
|
||||
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)));
|
||||
|
||||
@ -104,6 +110,8 @@ TEST_F(SerialListenerTests, listenForWorks) {
|
||||
listener.listenFor(listenForComparator, listenForCallback);
|
||||
|
||||
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_listen_count, 2);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user