├── .codedocs ├── .gitignore ├── async_comm-config.cmake ├── package.xml ├── .github └── workflows │ └── ci.yml ├── CHANGELOG.rst ├── LICENSE.md ├── CMakeLists.txt ├── include └── async_comm │ ├── util │ └── message_handler_ros.h │ ├── tcp_client.h │ ├── message_handler.h │ ├── serial.h │ ├── udp.h │ └── comm.h ├── examples ├── tcp_client_hello_world.cpp ├── udp_hello_world.cpp ├── serial_loopback.cpp └── serial_protocol.cpp ├── src ├── tcp_client.cpp ├── serial.cpp ├── udp.cpp └── comm.cpp ├── ros_prerelease_tests.py ├── README.md └── Doxyfile /.codedocs: -------------------------------------------------------------------------------- 1 | DOXYFILE = Doxyfile 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | *.user 4 | .vscode/ 5 | 6 | /build/ 7 | /doc/ 8 | -------------------------------------------------------------------------------- /async_comm-config.cmake: -------------------------------------------------------------------------------- 1 | get_filename_component(SELF_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 2 | include(${SELF_DIR}/async_comm-targets.cmake) 3 | get_filename_component(async_comm_INCLUDE_DIRS "${SELF_DIR}/../../include/async_comm" ABSOLUTE) 4 | set(async_comm_LIBRARIES async_comm) 5 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | async_comm 4 | 0.2.1 5 | A C++ library for asynchronous serial communication 6 | 7 | Daniel Koch 8 | BSD 9 | 10 | https://github.com/dpkoch/async_comm 11 | https://github.com/dpkoch/async_comm/issues 12 | 13 | cmake 14 | 15 | boost 16 | boost 17 | 18 | doxygen 19 | 20 | 21 | cmake 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | BUILD_TYPE: Release 11 | BUILD_EXAMPLES: ON 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Install Boost 21 | run: sudo apt-get install -y libboost-dev libboost-system-dev 22 | 23 | - name: Configure CMake 24 | run: | 25 | cmake -B ${{github.workspace}}/build \ 26 | -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ 27 | -DASYNC_COMM_BUILD_EXAMPLES=${{env.BUILD_EXAMPLES}} 28 | 29 | - name: Build 30 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 31 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package async_comm 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.2.1 (2021-01-21) 6 | ------------------ 7 | * Add noetic to ROS prerelease test distribution list 8 | * Fixes for compatibility with ROS2 workspaces: 9 | 10 | * cmake: Change install paths to basic lib/ and include/ 11 | * package.xml: Remove unneeded catkin dependency 12 | 13 | * Updated CMake examples in README 14 | * Contributors: Daniel Koch, Maciej Bogusz 15 | 16 | 0.2.0 (2020-03-16) 17 | ------------------ 18 | * Fix for UDP/TCP when loopback is only device with an address 19 | * Added listener interface 20 | * Added custom message handler functionality 21 | * TCPClient class implementing an async tcp client 22 | * Contributors: Daniel Koch, James Jackson, Rein Appeldoorn 23 | 24 | 0.1.1 (2019-02-21) 25 | ------------------ 26 | * Some cleanup 27 | * Process callbacks on a separate thread 28 | * Made buffer sizes constant, removed faulty mechanism for overriding 29 | * Removed requirement that bulk write length be no greater than write buffer size 30 | * Contributors: Daniel Koch 31 | 32 | 0.1.0 (2018-08-31) 33 | ------------------ 34 | * Initial release 35 | * Contributors: Daniel Koch, James Jackson 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018 Daniel Koch. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.2) 2 | project(async_comm) 3 | 4 | if (NOT DEFINED CMAKE_BUILD_TYPE) 5 | set(CMAKE_BUILD_TYPE Release) 6 | endif (NOT DEFINED CMAKE_BUILD_TYPE) 7 | 8 | find_package(Boost REQUIRED COMPONENTS system) 9 | find_package(Threads) 10 | 11 | add_library(${PROJECT_NAME} SHARED 12 | src/comm.cpp 13 | src/udp.cpp 14 | src/serial.cpp 15 | src/tcp_client.cpp 16 | ) 17 | target_include_directories(${PROJECT_NAME} PRIVATE ${Boost_INCLUDE_DIRS}) 18 | target_include_directories(${PROJECT_NAME} PUBLIC 19 | $ 20 | $ 21 | ) 22 | target_compile_options(${PROJECT_NAME} PRIVATE -std=c++11) 23 | target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) 24 | 25 | # examples 26 | option(ASYNC_COMM_BUILD_EXAMPLES "Build examples" OFF) 27 | 28 | if(ASYNC_COMM_BUILD_EXAMPLES) 29 | add_executable(udp_hello_world examples/udp_hello_world.cpp) 30 | target_link_libraries(udp_hello_world ${PROJECT_NAME}) 31 | 32 | add_executable(serial_loopback examples/serial_loopback.cpp) 33 | target_link_libraries(serial_loopback ${PROJECT_NAME}) 34 | 35 | add_executable(serial_protocol examples/serial_protocol.cpp) 36 | target_link_libraries(serial_protocol ${PROJECT_NAME}) 37 | 38 | add_executable(tcp_client_hello_world examples/tcp_client_hello_world.cpp) 39 | target_link_libraries(tcp_client_hello_world ${PROJECT_NAME}) 40 | endif(ASYNC_COMM_BUILD_EXAMPLES) 41 | 42 | install(TARGETS ${PROJECT_NAME} 43 | EXPORT ${PROJECT_NAME}-targets 44 | ARCHIVE DESTINATION lib 45 | LIBRARY DESTINATION lib 46 | ) 47 | install(DIRECTORY include/${PROJECT_NAME} 48 | DESTINATION include 49 | FILES_MATCHING PATTERN "*.h" 50 | ) 51 | 52 | # install CMake package configuration 53 | install(EXPORT ${PROJECT_NAME}-targets DESTINATION lib/${PROJECT_NAME}) 54 | install(FILES ${PROJECT_NAME}-config.cmake DESTINATION lib/${PROJECT_NAME}) 55 | 56 | # install package.xml for ROS release 57 | install(FILES package.xml DESTINATION share/${PROJECT_NAME}) 58 | -------------------------------------------------------------------------------- /include/async_comm/util/message_handler_ros.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2018 Daniel Koch. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file message_handler_ros.h 35 | * @author Daniel Koch 36 | */ 37 | 38 | #ifndef ASYNC_COMM_MESSAGE_HANDLER_ROS_H 39 | #define ASYNC_COMM_MESSAGE_HANDLER_ROS_H 40 | 41 | #include 42 | 43 | #include 44 | 45 | namespace async_comm 46 | { 47 | namespace util 48 | { 49 | 50 | /** 51 | * @class MessageHandlerROS 52 | * @brief Message handler implementation for ROS environments 53 | * 54 | * This is a convenience message handler implementation for ROS-based projects. 55 | * The implementation simply forwards messages to the appropriate rosconsole loggers. 56 | */ 57 | class MessageHandlerROS : public MessageHandler 58 | { 59 | public: 60 | inline void debug(const std::string &message) override { ROS_DEBUG("[async_comm]: %s", message.c_str()); } 61 | inline void info(const std::string &message) override { ROS_INFO("[async_comm]: %s", message.c_str()); } 62 | inline void warn(const std::string &message) override { ROS_WARN("[async_comm]: %s", message.c_str()); } 63 | inline void error(const std::string &message) override { ROS_ERROR("[async_comm]: %s", message.c_str()); } 64 | inline void fatal(const std::string &message) override { ROS_FATAL("[async_comm]: %s", message.c_str()); } 65 | }; 66 | 67 | } // namespace util 68 | } // namespace async_comm 69 | 70 | #endif // ASYNC_COMM_MESSAGE_HANDLER_ROS_H -------------------------------------------------------------------------------- /examples/tcp_client_hello_world.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2019 Rein Appeldoorn. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file tcp_client_hello_world.cpp 35 | * @author Rein Appeldoorn 36 | * 37 | * This example opens a TCP client that sends "hello world" messages. 38 | */ 39 | 40 | #include 41 | 42 | #include 43 | #include 44 | #include 45 | 46 | #include 47 | #include 48 | #include 49 | 50 | 51 | /** 52 | * @brief Callback function for the async_comm library 53 | * 54 | * Prints the received bytes to stdout. 55 | * 56 | * @param buf Received bytes buffer 57 | * @param len Number of bytes received 58 | */ 59 | void callback(const uint8_t* buf, size_t len) 60 | { 61 | for (size_t i = 0; i < len; i++) 62 | { 63 | std::cout << buf[i]; 64 | } 65 | } 66 | 67 | 68 | int main() 69 | { 70 | // open TCP connection 71 | async_comm::TCPClient tcp_client("localhost", 16140); 72 | tcp_client.register_receive_callback(&callback); 73 | 74 | if (!tcp_client.init()) 75 | { 76 | std::cout << "Failed to initialize TCP client" << std::endl; 77 | return 1; 78 | } 79 | 80 | // send message one direction 81 | for (size_t i = 0; i < 10; ++i) 82 | { 83 | std::string msg = "hello world " + std::to_string(i) + "!"; 84 | tcp_client.send_bytes((uint8_t*) msg.data(), msg.size()); 85 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 86 | } 87 | 88 | // close connection 89 | tcp_client.close(); 90 | 91 | return 0; 92 | } 93 | -------------------------------------------------------------------------------- /include/async_comm/tcp_client.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2019 Rein Appeldoorn. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file tcp_client.h 35 | * @author Rein Appeldoorn 36 | */ 37 | 38 | #ifndef ASYNC_COMM_TCP_CLIENT_H 39 | #define ASYNC_COMM_TCP_CLIENT_H 40 | 41 | #include 42 | 43 | #include 44 | #include 45 | 46 | #include 47 | #include 48 | 49 | namespace async_comm 50 | { 51 | 52 | /** 53 | * @class TCPClient 54 | * @brief Asynchronous communication class for a TCP client 55 | */ 56 | class TCPClient : public Comm 57 | { 58 | public: 59 | /** 60 | * @brief Connect to a TCP socket as a client 61 | * @param host The host where the TCP server is running 62 | * @param port The port on which the TCP server is listening 63 | * @param message_handler Custom message handler, or omit for default handler 64 | */ 65 | TCPClient(std::string host = DEFAULT_HOST, uint16_t port = DEFAULT_PORT, 66 | MessageHandler& message_handler = default_message_handler_); 67 | ~TCPClient(); 68 | 69 | private: 70 | static constexpr auto DEFAULT_HOST = "localhost"; 71 | static constexpr uint16_t DEFAULT_PORT = 16140; 72 | 73 | bool is_open() override; 74 | bool do_init() override; 75 | void do_close() override; 76 | void do_async_read(const boost::asio::mutable_buffers_1 &buffer, 77 | boost::function handler) override; 78 | void do_async_write(const boost::asio::const_buffers_1 &buffer, 79 | boost::function handler) override; 80 | 81 | std::string host_; 82 | uint16_t port_; 83 | 84 | boost::asio::ip::tcp::socket socket_; 85 | boost::asio::ip::tcp::endpoint endpoint_; 86 | }; 87 | 88 | } // namespace async_comm 89 | 90 | #endif // ASYNC_COMM_TCP_CLIENT_H 91 | -------------------------------------------------------------------------------- /include/async_comm/message_handler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2018 Daniel Koch. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file message_handler.h 35 | * @author Daniel Koch 36 | */ 37 | 38 | #ifndef ASYNC_COMM_MESSAGE_HANDLER_H 39 | #define ASYNC_COMM_MESSAGE_HANDLER_H 40 | 41 | #include 42 | #include 43 | 44 | namespace async_comm 45 | { 46 | 47 | /** 48 | * @class MessageHandler 49 | * @brief Abstract base class for message handler 50 | * 51 | * The implementations of this class define how messages are displayed, logged, 52 | * etc. To create custom behavior, derive from this base class and override the 53 | * pure virtual functions. 54 | */ 55 | class MessageHandler 56 | { 57 | public: 58 | virtual void debug(const std::string& message) = 0; 59 | virtual void info(const std::string& message) = 0; 60 | virtual void warn(const std::string& message) = 0; 61 | virtual void error(const std::string& message) = 0; 62 | virtual void fatal(const std::string& message) = 0; 63 | }; 64 | 65 | /** 66 | * @class DefaultMessageHandler 67 | * @brief Default message handler that outputs to stdout and stderr 68 | */ 69 | class DefaultMessageHandler : public MessageHandler 70 | { 71 | public: 72 | inline void debug(const std::string &message) override { std::cout << "[async_comm][DEBUG]: " << message << std::endl; } 73 | inline void info(const std::string &message) override { std::cout << "[async_comm][INFO]: " << message << std::endl; } 74 | inline void warn(const std::string &message) override { std::cerr << "[async_comm][WARN]: " << message << std::endl; } 75 | inline void error(const std::string &message) override { std::cerr << "[async_comm][ERROR]: " << message << std::endl; } 76 | inline void fatal(const std::string &message) override { std::cerr << "[async_comm][FATAL]: " << message << std::endl; } 77 | }; 78 | 79 | } // namespace async_comm 80 | 81 | #endif // ASYNC_COMM_MESSAGE_HANDLER_H -------------------------------------------------------------------------------- /include/async_comm/serial.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2018 Daniel Koch. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file serial.h 35 | * @author Daniel Koch 36 | */ 37 | 38 | #ifndef ASYNC_COMM_SERIAL_H 39 | #define ASYNC_COMM_SERIAL_H 40 | 41 | #include 42 | 43 | #include 44 | #include 45 | 46 | #include 47 | #include 48 | 49 | namespace async_comm 50 | { 51 | 52 | /** 53 | * @class Serial 54 | * @brief Asynchronous communication class for a serial port 55 | */ 56 | class Serial : public Comm 57 | { 58 | public: 59 | /** 60 | * @brief Open a serial port 61 | * @param port The port to open (e.g. "/dev/ttyUSB0") 62 | * @param baud_rate The baud rate for the serial port (e.g. 115200) 63 | * @param message_handler Custom message handler, or omit for default handler 64 | * 65 | */ 66 | Serial(std::string port, unsigned int baud_rate, MessageHandler& message_handler = default_message_handler_); 67 | ~Serial(); 68 | 69 | 70 | /** 71 | * @brief Set serial port baud rate 72 | * @param baud_rate The baud rate for the serial port (e.g. 115200) 73 | * @return True if successful 74 | */ 75 | bool set_baud_rate(unsigned int baud_rate); 76 | 77 | private: 78 | bool is_open() override; 79 | bool do_init() override; 80 | void do_close() override; 81 | void do_async_read(const boost::asio::mutable_buffers_1 &buffer, 82 | boost::function handler) override; 83 | void do_async_write(const boost::asio::const_buffers_1 &buffer, 84 | boost::function handler) override; 85 | 86 | std::string port_; 87 | unsigned int baud_rate_; 88 | 89 | boost::asio::serial_port serial_port_; 90 | }; 91 | 92 | } // namespace async_comm 93 | 94 | #endif // ASYNC_COMM_SERIAL_H 95 | -------------------------------------------------------------------------------- /src/tcp_client.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2019 Rein Appeldoorn. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file tcp_client.cpp 35 | * @author Rein Appeldoorn 36 | */ 37 | 38 | #include 39 | 40 | #include 41 | 42 | using boost::asio::ip::tcp; 43 | 44 | namespace async_comm 45 | { 46 | 47 | TCPClient::TCPClient(std::string host, uint16_t port, MessageHandler& message_handler) : 48 | Comm(message_handler), 49 | host_(host), 50 | port_(port), 51 | socket_(io_service_) 52 | { 53 | } 54 | 55 | TCPClient::~TCPClient() 56 | { 57 | do_close(); 58 | } 59 | 60 | bool TCPClient::is_open() 61 | { 62 | return socket_.is_open(); 63 | } 64 | 65 | bool TCPClient::do_init() 66 | { 67 | try 68 | { 69 | tcp::resolver resolver(io_service_); 70 | 71 | endpoint_ = *resolver.resolve({tcp::v4(), host_, "", boost::asio::ip::resolver_query_base::numeric_service}); 72 | endpoint_.port(port_); 73 | socket_.open(tcp::v4()); 74 | 75 | socket_.connect(endpoint_); 76 | 77 | socket_.set_option(tcp::socket::reuse_address(true)); 78 | socket_.set_option(tcp::socket::send_buffer_size(WRITE_BUFFER_SIZE*1024)); 79 | socket_.set_option(tcp::socket::receive_buffer_size(READ_BUFFER_SIZE*1024)); 80 | } 81 | catch (boost::system::system_error e) 82 | { 83 | message_handler_.error(e.what()); 84 | return false; 85 | } 86 | 87 | return true; 88 | } 89 | 90 | void TCPClient::do_close() 91 | { 92 | socket_.close(); 93 | } 94 | 95 | void TCPClient::do_async_read(const boost::asio::mutable_buffers_1 &buffer, 96 | boost::function handler) 97 | { 98 | socket_.async_receive(buffer, handler); 99 | } 100 | 101 | void TCPClient::do_async_write(const boost::asio::const_buffers_1 &buffer, 102 | boost::function handler) 103 | { 104 | socket_.async_send(buffer, handler); 105 | } 106 | 107 | } // namespace async_comm 108 | -------------------------------------------------------------------------------- /examples/udp_hello_world.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2018 Daniel Koch. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file udp_hello_world.cpp 35 | * @author Daniel Koch 36 | * 37 | * This example opens two UDP objects listening on different ports on the local host, and then uses each to send a 38 | * simple "hello world" message to the other. 39 | */ 40 | 41 | #include 42 | 43 | #include 44 | #include 45 | #include 46 | 47 | #include 48 | #include 49 | #include 50 | 51 | 52 | /** 53 | * @brief Callback function for the async_comm library 54 | * 55 | * Prints the received bytes to stdout. 56 | * 57 | * @param buf Received bytes buffer 58 | * @param len Number of bytes received 59 | */ 60 | void callback(const uint8_t* buf, size_t len) 61 | { 62 | for (size_t i = 0; i < len; i++) 63 | { 64 | std::cout << buf[i]; 65 | } 66 | } 67 | 68 | 69 | int main() 70 | { 71 | // open UDP ports 72 | async_comm::UDP udp1("localhost", 14620, "localhost", 14625); 73 | udp1.register_receive_callback(&callback); 74 | 75 | async_comm::UDP udp2("localhost", 14625, "localhost", 14620); 76 | udp2.register_receive_callback(&callback); 77 | 78 | if (!udp1.init() || !udp2.init()) 79 | { 80 | std::cout << "Failed to initialize UDP ports" << std::endl; 81 | return 1; 82 | } 83 | 84 | // send message one direction 85 | char message1[] = "hello world 1!"; 86 | udp2.send_bytes((uint8_t*) message1, std::strlen(message1)); 87 | 88 | // wait for all bytes to be received 89 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 90 | std::cout << std::endl << std::flush; 91 | 92 | // send message the other direction 93 | char message2[] = "hello world 2!"; 94 | udp1.send_bytes((uint8_t*) message2, std::strlen(message2)); 95 | 96 | // wait for all bytes to be received 97 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 98 | std::cout << std::endl << std::flush; 99 | 100 | std::cout.flush(); 101 | 102 | // close UDP ports 103 | udp1.close(); 104 | udp2.close(); 105 | 106 | return 0; 107 | } 108 | -------------------------------------------------------------------------------- /examples/serial_loopback.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2018 Daniel Koch. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file serial_loopback.cpp 35 | * @author Daniel Koch 36 | * 37 | * This example is designed for use with a USB-to-UART adapter with the RX and TX pins connected together (loopback). 38 | * Sends a series of bytes out and prints them to the console as they are received back. 39 | */ 40 | 41 | #include 42 | 43 | #include 44 | #include 45 | 46 | #include 47 | #include 48 | 49 | #define NUM_BYTES 64 50 | 51 | 52 | /** 53 | * @brief Callback function for the async_comm library 54 | * 55 | * Prints the received bytes to stdout. 56 | * 57 | * @param buf Received bytes buffer 58 | * @param len Number of bytes received 59 | */ 60 | void callback(const uint8_t* buf, size_t len) 61 | { 62 | for (size_t i = 0; i < len; i++) 63 | { 64 | std::printf("Received byte: %d\n", buf[i]); 65 | } 66 | } 67 | 68 | 69 | int main(int argc, char** argv) 70 | { 71 | // initialize 72 | char* port; 73 | if (argc < 2) 74 | { 75 | std::printf("USAGE: %s PORT\n", argv[0]); 76 | return 1; 77 | } 78 | else 79 | { 80 | std::printf("Using port %s\n", argv[1]); 81 | port = argv[1]; 82 | } 83 | 84 | // open serial port 85 | async_comm::Serial serial(port, 115200); 86 | serial.register_receive_callback(&callback); 87 | 88 | if (!serial.init()) 89 | { 90 | std::printf("Failed to initialize serial port\n"); 91 | return 2; 92 | } 93 | 94 | uint8_t buffer[NUM_BYTES]; 95 | 96 | // test sending bytes one at a time 97 | std::printf("Transmit individual bytes:\n"); 98 | for (uint8_t i = 0; i < NUM_BYTES; i++) 99 | { 100 | buffer[i] = i; 101 | serial.send_byte(i); 102 | } 103 | 104 | // wait for all bytes to be received 105 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 106 | 107 | // test sending all the bytes at once 108 | std::printf("Bulk transmit:\n"); 109 | serial.send_bytes(buffer, NUM_BYTES); 110 | 111 | // wait for all bytes to be received 112 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 113 | 114 | // close serial port 115 | serial.close(); 116 | 117 | return 0; 118 | } 119 | -------------------------------------------------------------------------------- /src/serial.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2018 Daniel Koch. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file serial.cpp 35 | * @author Daniel Koch 36 | */ 37 | 38 | #include 39 | 40 | #include 41 | 42 | using boost::asio::serial_port_base; 43 | 44 | namespace async_comm 45 | { 46 | 47 | Serial::Serial(std::string port, unsigned int baud_rate, MessageHandler& message_handler) : 48 | Comm(message_handler), 49 | port_(port), 50 | baud_rate_(baud_rate), 51 | serial_port_(io_service_) 52 | { 53 | } 54 | 55 | Serial::~Serial() 56 | { 57 | do_close(); 58 | } 59 | 60 | bool Serial::set_baud_rate(unsigned int baud_rate) 61 | { 62 | baud_rate_ = baud_rate; 63 | try 64 | { 65 | serial_port_.set_option(serial_port_base::baud_rate(baud_rate_)); 66 | } 67 | catch (boost::system::system_error e) 68 | { 69 | message_handler_.error(e.what()); 70 | return false; 71 | } 72 | 73 | return true; 74 | } 75 | 76 | bool Serial::is_open() 77 | { 78 | return serial_port_.is_open(); 79 | } 80 | 81 | bool Serial::do_init() 82 | { 83 | try 84 | { 85 | serial_port_.open(port_); 86 | serial_port_.set_option(serial_port_base::baud_rate(baud_rate_)); 87 | serial_port_.set_option(serial_port_base::character_size(8)); 88 | serial_port_.set_option(serial_port_base::parity(serial_port_base::parity::none)); 89 | serial_port_.set_option(serial_port_base::stop_bits(serial_port_base::stop_bits::one)); 90 | serial_port_.set_option(serial_port_base::flow_control(serial_port_base::flow_control::none)); 91 | } 92 | catch (boost::system::system_error e) 93 | { 94 | message_handler_.error(e.what()); 95 | return false; 96 | } 97 | 98 | return true; 99 | } 100 | 101 | void Serial::do_close() 102 | { 103 | serial_port_.close(); 104 | } 105 | 106 | void Serial::do_async_read(const boost::asio::mutable_buffers_1 &buffer, 107 | boost::function handler) 108 | { 109 | serial_port_.async_read_some(buffer, handler); 110 | } 111 | 112 | void Serial::do_async_write(const boost::asio::const_buffers_1 &buffer, 113 | boost::function handler) 114 | { 115 | serial_port_.async_write_some(buffer, handler); 116 | } 117 | 118 | } // namespace async_comm 119 | -------------------------------------------------------------------------------- /include/async_comm/udp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2018 Daniel Koch. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file udp.h 35 | * @author Daniel Koch 36 | */ 37 | 38 | #ifndef ASYNC_COMM_UDP_H 39 | #define ASYNC_COMM_UDP_H 40 | 41 | #include 42 | 43 | #include 44 | #include 45 | 46 | #include 47 | #include 48 | 49 | namespace async_comm 50 | { 51 | 52 | /** 53 | * @class UDP 54 | * @brief Asynchronous communication class for a UDP socket 55 | */ 56 | class UDP : public Comm 57 | { 58 | public: 59 | /** 60 | * @brief Bind a UDP socket 61 | * @param bind_host The bind host where this application is listening (usually "localhost") 62 | * @param bind_port The bind port where this application is listening 63 | * @param remote_host The remote host to communicate with 64 | * @param remote_port The port on the remote host 65 | * @param message_handler Custom message handler, or omit for default handler 66 | */ 67 | UDP(std::string bind_host = DEFAULT_BIND_HOST, uint16_t bind_port = DEFAULT_BIND_PORT, 68 | std::string remote_host = DEFAULT_REMOTE_HOST, uint16_t remote_port = DEFAULT_REMOTE_PORT, 69 | MessageHandler& message_handler = default_message_handler_); 70 | ~UDP(); 71 | 72 | private: 73 | static constexpr auto DEFAULT_BIND_HOST = "localhost"; 74 | static constexpr uint16_t DEFAULT_BIND_PORT = 16140; 75 | static constexpr auto DEFAULT_REMOTE_HOST = "localhost"; 76 | static constexpr uint16_t DEFAULT_REMOTE_PORT = 16145; 77 | 78 | bool is_open() override; 79 | bool do_init() override; 80 | void do_close() override; 81 | void do_async_read(const boost::asio::mutable_buffers_1 &buffer, 82 | boost::function handler) override; 83 | void do_async_write(const boost::asio::const_buffers_1 &buffer, 84 | boost::function handler) override; 85 | 86 | std::string bind_host_; 87 | uint16_t bind_port_; 88 | 89 | std::string remote_host_; 90 | uint16_t remote_port_; 91 | 92 | boost::asio::ip::udp::socket socket_; 93 | boost::asio::ip::udp::endpoint bind_endpoint_; 94 | boost::asio::ip::udp::endpoint remote_endpoint_; 95 | }; 96 | 97 | } // namespace async_comm 98 | 99 | #endif // ASYNC_COMM_UDP_H 100 | -------------------------------------------------------------------------------- /src/udp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2018 Daniel Koch. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file udp.cpp 35 | * @author Daniel Koch 36 | */ 37 | 38 | #include 39 | 40 | #include 41 | 42 | using boost::asio::ip::udp; 43 | 44 | namespace async_comm 45 | { 46 | 47 | 48 | UDP::UDP(std::string bind_host, uint16_t bind_port, std::string remote_host, uint16_t remote_port, 49 | MessageHandler& message_handler) : 50 | Comm(message_handler), 51 | bind_host_(bind_host), 52 | bind_port_(bind_port), 53 | remote_host_(remote_host), 54 | remote_port_(remote_port), 55 | socket_(io_service_) 56 | { 57 | } 58 | 59 | UDP::~UDP() 60 | { 61 | do_close(); 62 | } 63 | 64 | bool UDP::is_open() 65 | { 66 | return socket_.is_open(); 67 | } 68 | 69 | bool UDP::do_init() 70 | { 71 | try 72 | { 73 | udp::resolver resolver(io_service_); 74 | 75 | bind_endpoint_ = *resolver.resolve({udp::v4(), bind_host_, "", 76 | boost::asio::ip::resolver_query_base::numeric_service}); 77 | bind_endpoint_.port(bind_port_); 78 | 79 | remote_endpoint_ = *resolver.resolve({udp::v4(), remote_host_, "", 80 | boost::asio::ip::resolver_query_base::numeric_service}); 81 | remote_endpoint_.port(remote_port_); 82 | 83 | socket_.open(udp::v4()); 84 | socket_.bind(bind_endpoint_); 85 | 86 | socket_.set_option(udp::socket::reuse_address(true)); 87 | socket_.set_option(udp::socket::send_buffer_size(WRITE_BUFFER_SIZE*1024)); 88 | socket_.set_option(udp::socket::receive_buffer_size(READ_BUFFER_SIZE*1024)); 89 | } 90 | catch (boost::system::system_error e) 91 | { 92 | message_handler_.error(e.what()); 93 | return false; 94 | } 95 | 96 | return true; 97 | } 98 | 99 | void UDP::do_close() 100 | { 101 | socket_.close(); 102 | } 103 | 104 | void UDP::do_async_read(const boost::asio::mutable_buffers_1 &buffer, 105 | boost::function handler) 106 | { 107 | socket_.async_receive_from(buffer, remote_endpoint_, handler); 108 | } 109 | 110 | void UDP::do_async_write(const boost::asio::const_buffers_1 &buffer, 111 | boost::function handler) 112 | { 113 | socket_.async_send_to(buffer, remote_endpoint_, handler); 114 | } 115 | 116 | } // namespace async_comm 117 | -------------------------------------------------------------------------------- /src/comm.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2018 Daniel Koch. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file comm.cpp 35 | * @author Daniel Koch 36 | */ 37 | 38 | #include 39 | 40 | #include 41 | #include 42 | 43 | namespace async_comm 44 | { 45 | 46 | DefaultMessageHandler Comm::default_message_handler_; 47 | 48 | Comm::Comm(MessageHandler& message_handler) : 49 | message_handler_(message_handler), 50 | io_service_(), 51 | new_data_(false), 52 | shutdown_requested_(false), 53 | write_in_progress_(false) 54 | { 55 | } 56 | 57 | Comm::~Comm() 58 | { 59 | } 60 | 61 | bool Comm::init() 62 | { 63 | if (!do_init()) 64 | return false; 65 | 66 | callback_thread_ = std::thread(std::bind(&Comm::process_callbacks, this)); 67 | 68 | async_read(); 69 | io_thread_ = std::thread(boost::bind(&boost::asio::io_service::run, &this->io_service_)); 70 | 71 | return true; 72 | } 73 | 74 | void Comm::close() 75 | { 76 | // send shutdown signal to callback thread 77 | { 78 | std::unique_lock lock(callback_mutex_); 79 | shutdown_requested_ = true; 80 | } 81 | condition_variable_.notify_one(); 82 | 83 | io_service_.stop(); 84 | do_close(); 85 | 86 | if (io_thread_.joinable()) 87 | { 88 | io_thread_.join(); 89 | } 90 | 91 | if (callback_thread_.joinable()) 92 | { 93 | callback_thread_.join(); 94 | } 95 | } 96 | 97 | void Comm::send_bytes(const uint8_t *src, size_t len) 98 | { 99 | mutex_lock lock(write_mutex_); 100 | 101 | for (size_t pos = 0; pos < len; pos += WRITE_BUFFER_SIZE) 102 | { 103 | size_t num_bytes = (len - pos) > WRITE_BUFFER_SIZE ? WRITE_BUFFER_SIZE : (len - pos); 104 | write_queue_.emplace_back(src + pos, num_bytes); 105 | } 106 | 107 | async_write(true); 108 | } 109 | 110 | void Comm::register_receive_callback(std::function fun) 111 | { 112 | receive_callback_ = fun; 113 | } 114 | 115 | void Comm::register_listener(CommListener &listener) 116 | { 117 | listeners_.push_back(listener); 118 | } 119 | 120 | void Comm::async_read() 121 | { 122 | if (!is_open()) return; 123 | 124 | do_async_read(boost::asio::buffer(read_buffer_, READ_BUFFER_SIZE), 125 | boost::bind(&Comm::async_read_end, 126 | this, 127 | boost::asio::placeholders::error, 128 | boost::asio::placeholders::bytes_transferred)); 129 | } 130 | 131 | void Comm::async_read_end(const boost::system::error_code &error, size_t bytes_transferred) 132 | { 133 | if (error) 134 | { 135 | message_handler_.error(error.message()); 136 | close(); 137 | return; 138 | } 139 | 140 | { 141 | std::unique_lock lock(callback_mutex_); 142 | read_queue_.emplace_back(read_buffer_, bytes_transferred); 143 | new_data_ = true; 144 | } 145 | condition_variable_.notify_one(); 146 | 147 | async_read(); 148 | } 149 | 150 | void Comm::async_write(bool check_write_state) 151 | { 152 | if (check_write_state && write_in_progress_) 153 | return; 154 | 155 | mutex_lock lock(write_mutex_); 156 | if (write_queue_.empty()) 157 | return; 158 | 159 | write_in_progress_ = true; 160 | WriteBuffer& buffer = write_queue_.front(); 161 | do_async_write(boost::asio::buffer(buffer.dpos(), buffer.nbytes()), 162 | boost::bind(&Comm::async_write_end, 163 | this, 164 | boost::asio::placeholders::error, 165 | boost::asio::placeholders::bytes_transferred)); 166 | } 167 | 168 | void Comm::async_write_end(const boost::system::error_code &error, size_t bytes_transferred) 169 | { 170 | if (error) 171 | { 172 | message_handler_.error(error.message()); 173 | close(); 174 | return; 175 | } 176 | 177 | mutex_lock lock(write_mutex_); 178 | if (write_queue_.empty()) 179 | { 180 | write_in_progress_ = false; 181 | return; 182 | } 183 | 184 | WriteBuffer& buffer = write_queue_.front(); 185 | buffer.pos += bytes_transferred; 186 | if (buffer.nbytes() == 0) 187 | { 188 | write_queue_.pop_front(); 189 | } 190 | 191 | if (write_queue_.empty()) 192 | write_in_progress_ = false; 193 | else 194 | async_write(false); 195 | } 196 | 197 | void Comm::process_callbacks() 198 | { 199 | std::list local_queue; 200 | 201 | while (true) 202 | { 203 | // wait for either new data or a shutdown request 204 | std::unique_lock lock(callback_mutex_); 205 | condition_variable_.wait(lock, [this]{ return new_data_ || shutdown_requested_; }); 206 | 207 | // if shutdown requested, end thread execution 208 | if (shutdown_requested_) 209 | { 210 | break; 211 | } 212 | 213 | // move data to local buffer 214 | local_queue.splice(local_queue.end(), read_queue_); 215 | 216 | // release mutex to allow continued asynchronous read operations 217 | new_data_ = false; 218 | lock.unlock(); 219 | 220 | // execute callbacks for all new data 221 | while (!local_queue.empty()) 222 | { 223 | ReadBuffer buffer = local_queue.front(); 224 | if (receive_callback_) 225 | { 226 | receive_callback_(buffer.data, buffer.len); 227 | } 228 | for (std::reference_wrapper listener_ref : listeners_) 229 | { 230 | listener_ref.get().receive_callback(buffer.data, buffer.len); 231 | } 232 | local_queue.pop_front(); 233 | } 234 | } 235 | } 236 | 237 | } // namespace async_comm 238 | -------------------------------------------------------------------------------- /ros_prerelease_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import subprocess 5 | import sys 6 | 7 | import urllib.request 8 | import yaml 9 | 10 | import argparse 11 | 12 | from termcolor import cprint 13 | 14 | 15 | def print_blue(x): return cprint(x, 'blue', attrs=['bold']) 16 | def print_cyan(x): return cprint(x, 'cyan', attrs=['bold']) 17 | def print_green(x): return cprint(x, 'green', attrs=['bold']) 18 | def print_magenta(x): return cprint(x, 'magenta', attrs=['bold']) 19 | def print_red(x): return cprint(x, 'red', attrs=['bold']) 20 | def print_yellow(x): return cprint(x, 'yellow', attrs=['bold']) 21 | 22 | 23 | REPO_URL = 'https://raw.githubusercontent.com/ros-infrastructure/ros_buildfarm_config' 24 | BRANCH = 'production' 25 | INDEX_FILE = 'index.yaml' 26 | 27 | 28 | def run_test(target, branch): 29 | test_name = "%(ros_distro)s_%(os_distro)s_%(os_release)s_%(arch)s" % target 30 | 31 | config = target 32 | config['branch'] = branch 33 | config['test_name'] = test_name 34 | 35 | # move working directory into test folder 36 | os.mkdir(test_name) 37 | os.chdir(test_name) 38 | 39 | # generate prerelease scripts 40 | print("Generating prerelease scripts...") 41 | generate_prerelease_command = """ 42 | generate_prerelease_script.py \\ 43 | https://raw.githubusercontent.com/ros-infrastructure/ros_buildfarm_config/production/index.yaml \\ 44 | %(ros_distro)s default %(os_distro)s %(os_release)s %(arch)s \\ 45 | --custom-repo \\ 46 | async_com__custom-1:git:https://github.com/dpkoch/async_comm.git:%(branch)s \\ 47 | --level 0 \\ 48 | --output-dir ./ 49 | """ % config 50 | 51 | with open("../%s-generate.log" % (test_name), 'w') as log_file: 52 | process = subprocess.run( 53 | generate_prerelease_command, shell=True, stdout=log_file, stderr=subprocess.STDOUT) 54 | if process.returncode != 0: 55 | print_yellow("Generating prerelease scripts failed!") 56 | os.chdir('..') 57 | print_red("[Failed]") 58 | return False 59 | 60 | # run prerelease test 61 | print("Running prerelease tests...") 62 | with open("../%s-test.log" % (test_name), 'w') as log_file: 63 | process = subprocess.run( 64 | "./prerelease.sh", stdout=log_file, stderr=subprocess.STDOUT) 65 | os.chdir('..') 66 | if process.returncode == 0: 67 | print_green("[Passed]") 68 | return True 69 | else: 70 | print_red("[Failed]") 71 | return False 72 | 73 | 74 | def get_repo_file(repo_path): 75 | url = '/'.join([REPO_URL, BRANCH, repo_path]) 76 | with urllib.request.urlopen(url) as response: 77 | return response.read() 78 | 79 | 80 | def get_prerelease_targets(distros): 81 | targets = [] 82 | index_yaml = yaml.safe_load(get_repo_file(INDEX_FILE)) 83 | for ros_distro in distros: 84 | config_files = index_yaml['distributions'][ros_distro]['release_builds'] 85 | for file_path in config_files.values(): 86 | config_yaml = yaml.safe_load(get_repo_file(file_path)) 87 | for os_distro in config_yaml['targets']: 88 | for os_release in config_yaml['targets'][os_distro]: 89 | for arch in config_yaml['targets'][os_distro][os_release]: 90 | targets.append({'ros_distro': ros_distro, 91 | 'os_distro': os_distro, 92 | 'os_release': os_release, 93 | 'arch': arch}) 94 | return targets 95 | 96 | 97 | def filter_prerelease_targets(targets, os_distro, os_release, arch): 98 | def check_attribute(value, keys): 99 | if len(keys) > 0: 100 | return value in keys 101 | else: 102 | return True 103 | 104 | filtered_targets = [] 105 | for target in targets: 106 | if check_attribute(target['os_distro'], os_distro) \ 107 | and check_attribute(target['os_release'], os_release) \ 108 | and check_attribute(target['arch'], arch): 109 | filtered_targets.append(target) 110 | 111 | return filtered_targets 112 | 113 | 114 | def run_prerelease_tests(args): 115 | 116 | print_magenta("Running all tests for the \"%s\" branch" % (args.branch)) 117 | 118 | print() 119 | print_magenta("Running tests for the following ROS distros:") 120 | for distro in args.distro: 121 | print(distro) 122 | 123 | print() 124 | print("Retrieving release targets...") 125 | targets = filter_prerelease_targets(get_prerelease_targets( 126 | args.distro), args.os, args.release, args.arch) 127 | 128 | print() 129 | if len(targets) > 0: 130 | print_magenta("The following release targets will be tested:") 131 | for target in targets: 132 | print("%(ros_distro)s %(os_distro)s %(os_release)s %(arch)s" % target) 133 | else: 134 | print_yellow("No valid prerelease targets!") 135 | return False 136 | 137 | # run all tests and aggregate overall result into exit code 138 | failed_tests = 0 139 | for target in targets: 140 | print() 141 | print_blue( 142 | "Testing %(ros_distro)s %(os_distro)s %(os_release)s %(arch)s" % target) 143 | if not run_test(target, args.branch): 144 | failed_tests += 1 145 | 146 | # print and return overall result 147 | print() 148 | if failed_tests > 0: 149 | print_red("Failed %d of %d tests." % (failed_tests, len(targets))) 150 | return False 151 | else: 152 | print_green("Passed %d tests." % (len(targets))) 153 | return True 154 | 155 | 156 | if __name__ == '__main__': 157 | 158 | # process command line arguments 159 | parser = argparse.ArgumentParser(description='Run ROS prerelease tests') 160 | parser.add_argument( 161 | 'branch', help='The branch for which to run the prerelease tests') 162 | parser.add_argument('--distro', 163 | nargs='*', 164 | type=str, 165 | default=['kinetic', 'melodic', 'noetic'], 166 | help='A list of one or more ROS distros for which to run the prerelease tests (default: %(default)s)') 167 | parser.add_argument('--os', 168 | nargs='*', 169 | type=str, 170 | default=[], 171 | help='A list of one or more OS distros (e.g. ubuntu, debian) for which to run the prerelease tests') 172 | parser.add_argument('--release', 173 | nargs='*', 174 | type=str, 175 | default=[], 176 | help='A list of one or more OS releases (e.g. bionic, stretch) for which to run the prerelease tests') 177 | parser.add_argument('--arch', 178 | nargs='*', 179 | type=str, 180 | default=[], 181 | help='A list of one or more architectures (e.g. amd64, armhf) for which to run the prerelease tests') 182 | 183 | args = parser.parse_args() 184 | 185 | # check that working directory is empty 186 | if os.listdir('.'): 187 | print("Non-empty directory; please run in an empty directory. Aborting.") 188 | exit(3) 189 | 190 | exit_code = 0 if run_prerelease_tests(args) else 1 191 | print_cyan("Exit code: %d" % (exit_code)) 192 | exit(exit_code) 193 | -------------------------------------------------------------------------------- /include/async_comm/comm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2018 Daniel Koch. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file comm.h 35 | * @author Daniel Koch 36 | */ 37 | 38 | #ifndef ASYNC_COMM_COMM_H 39 | #define ASYNC_COMM_COMM_H 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | #include 50 | #include 51 | 52 | #include 53 | 54 | namespace async_comm 55 | { 56 | 57 | /** 58 | * @class CommListener 59 | * @brief Abstract base class for getting comm events via a listener interface 60 | */ 61 | class CommListener 62 | { 63 | public: 64 | /** 65 | * @brief Callback for data received 66 | * 67 | * @warning The data buffer passed to the callback function will be invalid after the callback function exits. If you 68 | * want to store the data for later processing, you must copy the data to a new buffer rather than storing the 69 | * pointer to the buffer. 70 | * 71 | * @param buf Address of buffer containing received bytes 72 | * @param size Number of bytes in the receive buffer 73 | */ 74 | virtual void receive_callback(const uint8_t * buf, size_t size) = 0; 75 | }; 76 | 77 | /** 78 | * @class Comm 79 | * @brief Abstract base class for an asynchronous communication port 80 | */ 81 | class Comm 82 | { 83 | public: 84 | /** 85 | * @brief Set up asynchronous communication base class 86 | * @param message_handler Custom message handler, or omit for default handler 87 | */ 88 | Comm(MessageHandler& message_handler = default_message_handler_); 89 | virtual ~Comm(); 90 | 91 | /** 92 | * @brief Initializes and opens the port 93 | * @return True if the port was succesfully initialized 94 | */ 95 | bool init(); 96 | 97 | /** 98 | * @brief Closes the port 99 | */ 100 | void close(); 101 | 102 | /** 103 | * @brief Send bytes from a buffer over the port 104 | * @param src Address of the buffer 105 | * @param len Number of bytes to send 106 | */ 107 | void send_bytes(const uint8_t * src, size_t len); 108 | 109 | /** 110 | * @brief Send a single byte over the port 111 | * @param data Byte to send 112 | */ 113 | inline void send_byte(uint8_t data) { send_bytes(&data, 1); } 114 | 115 | /** 116 | * @brief Register a callback function for when bytes are received on the port 117 | * 118 | * The callback function needs to accept two parameters. The first is of type `const uint8_t*`, and is a constant 119 | * pointer to the data buffer. The second is of type `size_t`, and specifies the number of bytes available in the 120 | * buffer. 121 | * 122 | * @warning The data buffer passed to the callback function will be invalid after the callback function exits. If you 123 | * want to store the data for later processing, you must copy the data to a new buffer rather than storing the 124 | * pointer to the buffer. 125 | * 126 | * @param fun Function to call when bytes are received 127 | */ 128 | void register_receive_callback(std::function fun); 129 | 130 | /** 131 | * @brief Register a listener for when bytes are received on the port 132 | * 133 | * The listener must inherit from CommListener and implement the `receive_callback` function. This is another 134 | * mechanism for receiving data from the Comm interface without needing to create function pointers. Multiple 135 | * listeners can be added and all will get the callback 136 | * 137 | * @param listener Reference to listener 138 | */ 139 | void register_listener(CommListener &listener); 140 | 141 | protected: 142 | 143 | static constexpr size_t READ_BUFFER_SIZE = 1024; 144 | static constexpr size_t WRITE_BUFFER_SIZE = 1024; 145 | 146 | static DefaultMessageHandler default_message_handler_; 147 | 148 | virtual bool is_open() = 0; 149 | virtual bool do_init() = 0; 150 | virtual void do_close() = 0; 151 | virtual void do_async_read(const boost::asio::mutable_buffers_1 &buffer, 152 | boost::function handler) = 0; 153 | virtual void do_async_write(const boost::asio::const_buffers_1 &buffer, 154 | boost::function handler) = 0; 155 | 156 | MessageHandler& message_handler_; 157 | boost::asio::io_service io_service_; 158 | 159 | private: 160 | 161 | struct ReadBuffer 162 | { 163 | uint8_t data[READ_BUFFER_SIZE]; 164 | size_t len; 165 | 166 | ReadBuffer(const uint8_t * buf, size_t len) : len(len) 167 | { 168 | assert(len <= READ_BUFFER_SIZE); // only checks in debug mode 169 | memcpy(data, buf, len); 170 | } 171 | }; 172 | 173 | struct WriteBuffer 174 | { 175 | uint8_t data[WRITE_BUFFER_SIZE]; 176 | size_t len; 177 | size_t pos; 178 | 179 | WriteBuffer() : len(0), pos(0) {} 180 | 181 | WriteBuffer(const uint8_t * buf, size_t len) : len(len), pos(0) 182 | { 183 | assert(len <= WRITE_BUFFER_SIZE); // only checks in debug mode 184 | memcpy(data, buf, len); 185 | } 186 | 187 | const uint8_t * dpos() const { return data + pos; } 188 | 189 | size_t nbytes() const { return len - pos; } 190 | }; 191 | 192 | typedef std::lock_guard mutex_lock; 193 | 194 | void async_read(); 195 | void async_read_end(const boost::system::error_code& error, size_t bytes_transferred); 196 | 197 | void async_write(bool check_write_state); 198 | void async_write_end(const boost::system::error_code& error, size_t bytes_transferred); 199 | 200 | void process_callbacks(); 201 | 202 | std::thread io_thread_; 203 | std::thread callback_thread_; 204 | 205 | uint8_t read_buffer_[READ_BUFFER_SIZE]; 206 | std::list read_queue_; 207 | std::mutex callback_mutex_; 208 | std::condition_variable condition_variable_; 209 | bool new_data_; 210 | bool shutdown_requested_; 211 | 212 | std::list write_queue_; 213 | std::recursive_mutex write_mutex_; 214 | bool write_in_progress_; 215 | 216 | std::function receive_callback_ = nullptr; 217 | std::vector> listeners_; 218 | }; 219 | 220 | } // namespace async_comm 221 | 222 | #endif // ASYNC_COMM_COMM_H 223 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Async Comm Library 2 | 3 | ![CI Status](https://github.com/dpkoch/async_comm/actions/workflows/ci.yml/badge.svg?branch=master) 4 | [![ROS Buildfarm Status](http://build.ros.org/buildStatus/icon?job=Mdev__async_comm__ubuntu_bionic_amd64&subject=ROS%20Buildfarm)](http://build.ros.org/job/Mdev__async_comm__ubuntu_bionic_amd64) 5 | [![Documentation Status](https://codedocs.xyz/dpkoch/async_comm.svg)](https://codedocs.xyz/dpkoch/async_comm/) 6 | 7 | This project provides a C++ library that gives a simple interface for asynchronous serial communications over a serial port or UDP. 8 | It uses the [Boost.Asio](http://www.boost.org/doc/libs/master/doc/html/boost_asio.html) library under the hood, but hides from the user the details of interfacing with the ports or sockets and managing send/receive buffers. 9 | 10 | ## Including in your project 11 | 12 | There are three ways to use the `async_comm` library in your project: 13 | 14 | 1. If you'll be using the library in a ROS package, install from the ROS repositories 15 | 2. Build and install the library on your system, then use CMake's `find_package()` functionality 16 | 3. Include the async_comm as a submodule in your project 17 | 18 | With the second and third options, you will need to ensure that the Boost library is installed before proceeding: 19 | 20 | ```bash 21 | sudo apt -y install libboost-dev libboost-system-dev 22 | ``` 23 | 24 | ### ROS install 25 | 26 | The `async_comm` library is released as a third-party, non-catkin package for ROS following the guidelines in [REP 136](http://www.ros.org/reps/rep-0136.html). To use the library in your ROS package, first install the library from the ROS repositories: 27 | 28 | ```bash 29 | sudo apt install ros--async-comm 30 | ``` 31 | 32 | Replace `` with your ROS distribution. The library is currently released for kinetic, lunar, and melodic. 33 | 34 | Then, add something like the following lines to your package's CMakeLists.txt: 35 | 36 | ```CMake 37 | # ... 38 | 39 | find_package(async_comm REQUIRED) 40 | 41 | catkin_package( 42 | # ... 43 | DEPENDS async_comm 44 | ) 45 | 46 | # ... 47 | 48 | add_executable(my_node src/my_node.cpp) 49 | target_link_libraries(my_node ${async_comm_LIBRARIES}) 50 | ``` 51 | 52 | Also be sure to list `async_comm` as a dependency in your package.xml: 53 | 54 | ```XML 55 | 56 | 57 | ... 58 | async_comm 59 | ... 60 | 61 | ``` 62 | 63 | ### System install 64 | 65 | First, download and install the library: 66 | 67 | ```bash 68 | git clone https://github.com/dpkoch/async_comm.git 69 | cd async_comm 70 | mkdir build && cd build/ 71 | cmake .. && make 72 | sudo make install 73 | ``` 74 | 75 | Then, do something like this in your project's CMakeLists.txt: 76 | 77 | ```CMake 78 | cmake_minimum_required(VERSION 3.0.2) 79 | project(my_project) 80 | 81 | find_package(async_comm REQUIRED) 82 | 83 | add_executable(my_project src/my_project.cpp) 84 | target_link_libraries(my_project ${async_comm_LIBRARIES}) 85 | ``` 86 | 87 | ### Including as a submodule 88 | 89 | If you don't want to go with the ROS or system install options, the next easiest way to embed the `async_comm` library in your project is as a [Git submodule](https://git-scm.com/docs/gitsubmodules). The following instructions are for a project using Git for version control and CMake for a build system, but should serve as a starting point for other setups. 90 | 91 | For example, to put `async_comm` in the `lib/async_comm directory`, run the following from the root of your project: 92 | 93 | ```bash 94 | git submodule add https://github.com/dpkoch/async_comm.git lib/async_comm 95 | ``` 96 | 97 | Your CMakeLists.txt file would then look something like this: 98 | 99 | ```CMake 100 | cmake_minimum_required(VERSION 3.0.2) 101 | project(my_project) 102 | 103 | add_subdirectory(lib/async_comm) 104 | 105 | add_executable(my_project src/my_project.cpp) 106 | target_link_libraries(my_project async_comm) 107 | ``` 108 | 109 | ## Usage 110 | 111 | There are three classes that you'll use directly as a user: 112 | 113 | - `async_comm::Serial`: for communication over a serial port 114 | - `async_comm::UDP`: for communication over a UDP socket 115 | - `async_comm::TCPClient`: for communication over a TCP socket (client) 116 | 117 | All classes have the same interface and inherit from the `async_comm::Comm` base class. 118 | The constructors for each class require the arguments to specify the details of the serial port, UDP or TCP socket. 119 | 120 | The interface consists of the following functions: 121 | 122 | - `bool init()`: initializes and opens the port or socket 123 | - `void register_receive_callback(std::function fun)`: register a user-defined function to handle received bytes; this function will be called by the `Serial`, `UDP` or `TCPClient` object every time a new data is received 124 | - `void send_bytes(const uint8_t * src, size_t len)`: send the specified number of bytes from the specified source buffer 125 | - `void close()`: close the port or socket 126 | 127 | More details can be found in the [code API documentation](https://codedocs.xyz/dpkoch/async_comm/). 128 | Very simple example programs are provided to illustrate the usage as described below. 129 | 130 | One tricky part is registering the member function of a class as the receive callback. This is accomplished using `std::bind`. For example, if I want to register the `receive` function of `MyClass` from within the class, I would use 131 | 132 | ```C++ 133 | serial_.register_receive_callback(std::bind(&MyClass::receive, this, std::placeholders::_1, std::placeholders::_2)); 134 | ``` 135 | 136 | where `serial_` is an instance of `async_comm::Serial`. 137 | 138 | ## Message Handlers 139 | 140 | It is possible to implement custom handlers for the error messages and other messages produced by the library. To create a message handler, simply inherit from the `MessageHandler` abstract base class defined in `include/async_comm/message_handler.h`, and override the pure virtual functions. 141 | 142 | Each of the user-facing classes accepts, as an optional final argument, a reference to a class that derives from `MessageHandler`. To use a custom message handler, simply create an instance of your handler and pass it as that optional argument. When that argument is omitted, the library uses a default message handler that prints to `stdout` and `stderr`. 143 | 144 | A custom message handler can be especially useful, for example, when the library is used as part of a ROS node and you wish to forward the error messages to the rosconsole logging functionality. A convenience class `MessageHandlerROS` has been provided for this purpose. To use this handler, do something like the following: 145 | 146 | ```C++ 147 | #include 148 | #include 149 | 150 | #include 151 | 152 | // ... 153 | 154 | async_comm::util::MessageHandlerROS rosconsole_handler; 155 | async_comm::Serial serial("/dev/ttyUSB0", 115200, rosconsole_handler); 156 | 157 | // ... 158 | ``` 159 | 160 | ## Examples 161 | 162 | There are three examples provided in the repository. The first two are very simple, while the third is more complete. To build the examples, run CMake with the `-DASYNC_COMM_BUILD_EXAMPLES=ON` flag. 163 | 164 | - `examples/serial_loopback.cpp`: Designed for use with a USB-to-UART adapter with the RX and TX pins connected together (loopback). Sends a series of bytes out and prints them to the console as they are received back. 165 | - `examples/udp_hello_world.cpp`: Opens two UDP objects listening on different ports on the local host, and then uses each to send a simple "hello world" message to the other. 166 | - `examples/serial_protocol.cpp`: Implements a simple serial protocol and parser for a message that includes two integer values, including a cyclic reduncancy check. Tests the protocol and `async_comm` library over a serial loopback. 167 | - `examples/tcp_client_hello_world.cpp`: Opens a TCP client that sends "hello world" messages. Example only runs with a valid running TCP/IP server. Server can be started using [netcat](https://en.wikipedia.org/wiki/Netcat): `nc -l 16140`. 168 | -------------------------------------------------------------------------------- /examples/serial_protocol.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (BSD-3 License) 3 | * 4 | * Copyright (c) 2018 Daniel Koch. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /** 34 | * @file serial_protocol.cpp 35 | * @author Daniel Koch 36 | * 37 | * This example implements a simple serial protocol, and tests the async_comm library using that protocol on a serial 38 | * loopback (USB-to-UART converter with the RX and TX pins connected together). 39 | * 40 | * The message defined by the serial protocol has the following format: 41 | * 42 | * | Field | Type | Size (bytes) | Description | 43 | * |------------|----------|--------------|------------------------------------------------------| 44 | * | Start Byte | | 1 | Identifies the beginning of a message, value is 0xA5 | 45 | * | `id` | uint32_t | 4 | Sequential message ID | 46 | * | `v1` | uint32_t | 4 | The first data field | 47 | * | `v2` | uint32_t | 4 | The second data field | 48 | * | CRC | uint8_t | 1 | Cyclic redundancy check (CRC) byte | 49 | * 50 | * The "payload" of the message is the part that contains the actual data, and consists of the `id`, `v1`, and `v2` 51 | * fields. 52 | * 53 | * The parser is implemented as a finite state machine. 54 | */ 55 | 56 | #include 57 | 58 | #include 59 | #include 60 | 61 | #include 62 | #include 63 | #include 64 | 65 | // specify the number of messages to send 66 | #define NUM_MSGS 40000 67 | 68 | // define attributes of the serial protocol 69 | #define START_BYTE 0xA5 70 | 71 | #define START_BYTE_LEN 1 72 | #define PAYLOAD_LEN 12 73 | #define CRC_LEN 1 74 | #define PACKET_LEN (START_BYTE_LEN + PAYLOAD_LEN + CRC_LEN) 75 | 76 | // specify relevant serial port options 77 | #define BAUD_RATE 921600 78 | #define NUM_START_BITS 1 79 | #define NUM_STOP_BITS 1 80 | 81 | 82 | /** 83 | * @brief Recursively update the cyclic redundancy check (CRC) 84 | * 85 | * This uses the CRC-8-CCITT polynomial. 86 | * 87 | * Source: http://www.nongnu.org/avr-libc/user-manual/group__util__crc.html#gab27eaaef6d7fd096bd7d57bf3f9ba083 88 | * 89 | * @param inCrc The current CRC value. This should be initialized to 0 before processing first byte. 90 | * @param inData The byte being processed 91 | * @return The new CRC value 92 | */ 93 | uint8_t update_crc(uint8_t inCrc, uint8_t inData) 94 | { 95 | uint8_t i; 96 | uint8_t data; 97 | 98 | data = inCrc ^ inData; 99 | 100 | for ( i = 0; i < 8; i++ ) 101 | { 102 | if (( data & 0x80 ) != 0 ) 103 | { 104 | data <<= 1; 105 | data ^= 0x07; 106 | } 107 | else 108 | { 109 | data <<= 1; 110 | } 111 | } 112 | return data; 113 | } 114 | 115 | 116 | /** 117 | * @brief Pack message contents into a buffer 118 | * @param[out] dst Buffer in which to store the message 119 | * @param[in] id ID field of the message 120 | * @param[in] v1 First data field of the message 121 | * @param[in] v2 Second data field of the message 122 | * 123 | * @post The specified buffer contains the complete message packet, including start byte and CRC byte 124 | */ 125 | void pack_message(uint8_t* dst, uint32_t id, uint32_t v1, uint32_t v2) 126 | { 127 | dst[0] = START_BYTE; 128 | memcpy(dst+1, &id, 4); 129 | memcpy(dst+5, &v1, 4); 130 | memcpy(dst+9, &v2, 4); 131 | 132 | uint8_t crc = 0; 133 | for (size_t i = 0; i < PACKET_LEN-1; i++) 134 | { 135 | crc = update_crc(crc, dst[i]); 136 | } 137 | dst[PACKET_LEN-1] = crc; 138 | } 139 | 140 | 141 | /** 142 | * @brief Unpack the contents of a message payload buffer 143 | * @param[in] src The buffer to unpack 144 | * @param[out] id ID field of the message 145 | * @param[out] v1 First data field of the message 146 | * @param[out] v2 Second data field of the message 147 | * 148 | * @pre Buffer contains a valid message payload 149 | * @post Payload contents have been placed into the specified variables 150 | */ 151 | void unpack_payload(uint8_t* src, uint32_t *id, uint32_t *v1, uint32_t *v2) 152 | { 153 | memcpy(id, src, 4); 154 | memcpy(v1, src+4, 4); 155 | memcpy(v2, src+8, 4); 156 | } 157 | 158 | 159 | /** 160 | * @brief States for the parser state machine 161 | */ 162 | enum ParseState 163 | { 164 | PARSE_STATE_IDLE, 165 | PARSE_STATE_GOT_START_BYTE, 166 | PARSE_STATE_GOT_PAYLOAD 167 | }; 168 | 169 | ParseState parse_state = PARSE_STATE_IDLE; //!< Current state of the parser state machine 170 | uint8_t receive_buffer[PAYLOAD_LEN]; //!< Buffer for accumulating received payload 171 | 172 | volatile int receive_count = 0; //!< Keeps track of how many valid messages have been received 173 | bool received[NUM_MSGS]; //!< Keeps track of which messages we've received back 174 | 175 | std::mutex mutex; //!< mutex for synchronization between the main thread and callback thread 176 | std::condition_variable condition_variable; //!< condition variable used to suspend main thread until all messages have been received back 177 | volatile bool all_messages_received = false; //!< flag for whether all messages have been received back 178 | 179 | 180 | /** 181 | * @brief Passes a received byte through the parser state machine 182 | * @param byte The byte to process 183 | */ 184 | void parse_byte(uint8_t byte) 185 | { 186 | static size_t payload_count; 187 | static uint8_t crc; 188 | 189 | switch (parse_state) 190 | { 191 | case PARSE_STATE_IDLE: 192 | if (byte == START_BYTE) 193 | { 194 | payload_count = 0; 195 | crc = 0; 196 | crc = update_crc(crc, byte); 197 | 198 | parse_state = PARSE_STATE_GOT_START_BYTE; 199 | } 200 | break; 201 | case PARSE_STATE_GOT_START_BYTE: 202 | receive_buffer[payload_count] = byte; 203 | crc = update_crc(crc, byte); 204 | if (++payload_count >= PAYLOAD_LEN) 205 | { 206 | parse_state = PARSE_STATE_GOT_PAYLOAD; 207 | } 208 | break; 209 | case PARSE_STATE_GOT_PAYLOAD: 210 | if (byte == crc) 211 | { 212 | uint32_t id, v1, v2; 213 | unpack_payload(receive_buffer, &id, &v1, &v2); 214 | received[id] = true; 215 | receive_count++; 216 | 217 | // notify the main thread when all messages have been received 218 | if (receive_count >= NUM_MSGS) 219 | { 220 | { 221 | std::unique_lock lock(mutex); 222 | all_messages_received = true; 223 | } 224 | condition_variable.notify_one(); 225 | } 226 | } // otherwise ignore it 227 | parse_state = PARSE_STATE_IDLE; 228 | break; 229 | } 230 | } 231 | 232 | 233 | /** 234 | * @brief Callback function for the async_comm library 235 | * 236 | * Passes the received bytes through the parser state machine. 237 | * 238 | * @param buf Received bytes buffer 239 | * @param len Number of bytes received 240 | */ 241 | void callback(const uint8_t* buf, size_t len) 242 | { 243 | for (size_t i = 0; i < len; i++) 244 | { 245 | parse_byte(buf[i]); 246 | } 247 | } 248 | 249 | 250 | int main(int argc, char** argv) 251 | { 252 | // initialize 253 | char* port; 254 | if (argc < 2) 255 | { 256 | std::printf("USAGE: %s PORT\n", argv[0]); 257 | return 1; 258 | } 259 | else 260 | { 261 | std::printf("Using port %s\n", argv[1]); 262 | port = argv[1]; 263 | } 264 | 265 | // open serial port 266 | async_comm::Serial serial(port, BAUD_RATE); 267 | serial.register_receive_callback(&callback); 268 | 269 | if (!serial.init()) 270 | { 271 | std::printf("Failed to initialize serial port\n"); 272 | return 2; 273 | } 274 | 275 | // initialize variable for tracking which messages we've received 276 | memset(received, 0, sizeof(received)); 277 | 278 | auto start = std::chrono::high_resolution_clock::now(); 279 | 280 | // pack and send the specified number of messages with unique IDs and data 281 | uint8_t buffer[PACKET_LEN]; 282 | for (uint32_t i = 0; i < NUM_MSGS; i++) 283 | { 284 | pack_message(buffer, i, i*2, i*4); 285 | serial.send_bytes(buffer, PACKET_LEN); 286 | } 287 | auto finish_write = std::chrono::high_resolution_clock::now(); 288 | 289 | // wait to receive all messages 290 | { 291 | std::unique_lock lock(mutex); 292 | condition_variable.wait(lock, []{ return all_messages_received; }); 293 | } 294 | 295 | auto finish_read = std::chrono::high_resolution_clock::now(); 296 | 297 | // close serial port 298 | serial.close(); 299 | 300 | // did we get all the messages back? 301 | int num_received = 0; 302 | for (int i = 0; i < NUM_MSGS; i++) 303 | { 304 | if (received[i]) 305 | { 306 | num_received++; 307 | } 308 | else 309 | { 310 | std::printf("Missing message %d\n", i); 311 | } 312 | } 313 | 314 | // evaluate and print performance 315 | std::chrono::duration write_time = finish_write - start; 316 | std::chrono::duration read_time = finish_read - start; 317 | 318 | std::printf("Received %d of %d messages\n", num_received, NUM_MSGS); 319 | std::printf("Elapsed write time: %fms\n", write_time.count()); 320 | std::printf("Elapsed read time: %fms\n", read_time.count()); 321 | 322 | int num_bytes = NUM_MSGS * PACKET_LEN; 323 | double expected_time = num_bytes * (8 + NUM_START_BITS + NUM_STOP_BITS) / (double) BAUD_RATE; 324 | std::printf("Expected read time: %fms\n", expected_time*1e3); 325 | std::printf("Total: %d bytes\n", num_bytes); 326 | 327 | return 0; 328 | } 329 | -------------------------------------------------------------------------------- /Doxyfile: -------------------------------------------------------------------------------- 1 | # Doxyfile 1.8.11 2 | 3 | #--------------------------------------------------------------------------- 4 | # Project related configuration options 5 | #--------------------------------------------------------------------------- 6 | DOXYFILE_ENCODING = UTF-8 7 | PROJECT_NAME = "Async Comm" 8 | PROJECT_NUMBER = 9 | PROJECT_BRIEF = "A library for asynchronous serial communication" 10 | PROJECT_LOGO = 11 | OUTPUT_DIRECTORY = "doc" 12 | CREATE_SUBDIRS = NO 13 | ALLOW_UNICODE_NAMES = NO 14 | OUTPUT_LANGUAGE = English 15 | BRIEF_MEMBER_DESC = YES 16 | REPEAT_BRIEF = YES 17 | ABBREVIATE_BRIEF = 18 | ALWAYS_DETAILED_SEC = NO 19 | INLINE_INHERITED_MEMB = NO 20 | FULL_PATH_NAMES = YES 21 | STRIP_FROM_PATH = 22 | STRIP_FROM_INC_PATH = 23 | SHORT_NAMES = NO 24 | JAVADOC_AUTOBRIEF = NO 25 | QT_AUTOBRIEF = NO 26 | MULTILINE_CPP_IS_BRIEF = NO 27 | INHERIT_DOCS = YES 28 | SEPARATE_MEMBER_PAGES = NO 29 | TAB_SIZE = 2 30 | ALIASES = 31 | TCL_SUBST = 32 | OPTIMIZE_OUTPUT_FOR_C = NO 33 | OPTIMIZE_OUTPUT_JAVA = NO 34 | OPTIMIZE_FOR_FORTRAN = NO 35 | OPTIMIZE_OUTPUT_VHDL = NO 36 | EXTENSION_MAPPING = 37 | MARKDOWN_SUPPORT = YES 38 | AUTOLINK_SUPPORT = YES 39 | BUILTIN_STL_SUPPORT = NO 40 | CPP_CLI_SUPPORT = NO 41 | SIP_SUPPORT = NO 42 | IDL_PROPERTY_SUPPORT = YES 43 | DISTRIBUTE_GROUP_DOC = NO 44 | GROUP_NESTED_COMPOUNDS = NO 45 | SUBGROUPING = YES 46 | INLINE_GROUPED_CLASSES = NO 47 | INLINE_SIMPLE_STRUCTS = NO 48 | TYPEDEF_HIDES_STRUCT = NO 49 | LOOKUP_CACHE_SIZE = 0 50 | #--------------------------------------------------------------------------- 51 | # Build related configuration options 52 | #--------------------------------------------------------------------------- 53 | EXTRACT_ALL = NO 54 | EXTRACT_PRIVATE = NO 55 | EXTRACT_PACKAGE = NO 56 | EXTRACT_STATIC = NO 57 | EXTRACT_LOCAL_CLASSES = YES 58 | EXTRACT_LOCAL_METHODS = NO 59 | EXTRACT_ANON_NSPACES = NO 60 | HIDE_UNDOC_MEMBERS = NO 61 | HIDE_UNDOC_CLASSES = NO 62 | HIDE_FRIEND_COMPOUNDS = NO 63 | HIDE_IN_BODY_DOCS = NO 64 | INTERNAL_DOCS = NO 65 | CASE_SENSE_NAMES = YES 66 | HIDE_SCOPE_NAMES = NO 67 | HIDE_COMPOUND_REFERENCE= NO 68 | SHOW_INCLUDE_FILES = YES 69 | SHOW_GROUPED_MEMB_INC = NO 70 | FORCE_LOCAL_INCLUDES = NO 71 | INLINE_INFO = YES 72 | SORT_MEMBER_DOCS = YES 73 | SORT_BRIEF_DOCS = NO 74 | SORT_MEMBERS_CTORS_1ST = NO 75 | SORT_GROUP_NAMES = NO 76 | SORT_BY_SCOPE_NAME = NO 77 | STRICT_PROTO_MATCHING = NO 78 | GENERATE_TODOLIST = YES 79 | GENERATE_TESTLIST = YES 80 | GENERATE_BUGLIST = YES 81 | GENERATE_DEPRECATEDLIST= YES 82 | ENABLED_SECTIONS = 83 | MAX_INITIALIZER_LINES = 30 84 | SHOW_USED_FILES = YES 85 | SHOW_FILES = YES 86 | SHOW_NAMESPACES = YES 87 | FILE_VERSION_FILTER = 88 | LAYOUT_FILE = 89 | CITE_BIB_FILES = 90 | #--------------------------------------------------------------------------- 91 | # Configuration options related to warning and progress messages 92 | #--------------------------------------------------------------------------- 93 | QUIET = NO 94 | WARNINGS = YES 95 | WARN_IF_UNDOCUMENTED = YES 96 | WARN_IF_DOC_ERROR = YES 97 | WARN_NO_PARAMDOC = NO 98 | WARN_AS_ERROR = NO 99 | WARN_FORMAT = "$file:$line: $text" 100 | WARN_LOGFILE = 101 | #--------------------------------------------------------------------------- 102 | # Configuration options related to the input files 103 | #--------------------------------------------------------------------------- 104 | INPUT = include src examples README.md 105 | INPUT_ENCODING = UTF-8 106 | FILE_PATTERNS = 107 | RECURSIVE = YES 108 | EXCLUDE = 109 | EXCLUDE_SYMLINKS = NO 110 | EXCLUDE_PATTERNS = 111 | EXCLUDE_SYMBOLS = 112 | EXAMPLE_PATH = 113 | EXAMPLE_PATTERNS = 114 | EXAMPLE_RECURSIVE = NO 115 | IMAGE_PATH = 116 | INPUT_FILTER = 117 | FILTER_PATTERNS = 118 | FILTER_SOURCE_FILES = NO 119 | FILTER_SOURCE_PATTERNS = 120 | USE_MDFILE_AS_MAINPAGE = README.md 121 | #--------------------------------------------------------------------------- 122 | # Configuration options related to source browsing 123 | #--------------------------------------------------------------------------- 124 | SOURCE_BROWSER = YES 125 | INLINE_SOURCES = NO 126 | STRIP_CODE_COMMENTS = YES 127 | REFERENCED_BY_RELATION = NO 128 | REFERENCES_RELATION = NO 129 | REFERENCES_LINK_SOURCE = YES 130 | SOURCE_TOOLTIPS = YES 131 | USE_HTAGS = NO 132 | VERBATIM_HEADERS = YES 133 | CLANG_ASSISTED_PARSING = NO 134 | CLANG_OPTIONS = 135 | #--------------------------------------------------------------------------- 136 | # Configuration options related to the alphabetical class index 137 | #--------------------------------------------------------------------------- 138 | ALPHABETICAL_INDEX = YES 139 | COLS_IN_ALPHA_INDEX = 5 140 | IGNORE_PREFIX = 141 | #--------------------------------------------------------------------------- 142 | # Configuration options related to the HTML output 143 | #--------------------------------------------------------------------------- 144 | GENERATE_HTML = YES 145 | HTML_OUTPUT = html 146 | HTML_FILE_EXTENSION = .html 147 | HTML_HEADER = 148 | HTML_FOOTER = 149 | HTML_STYLESHEET = 150 | HTML_EXTRA_STYLESHEET = 151 | HTML_EXTRA_FILES = 152 | HTML_COLORSTYLE_HUE = 220 153 | HTML_COLORSTYLE_SAT = 100 154 | HTML_COLORSTYLE_GAMMA = 80 155 | HTML_TIMESTAMP = NO 156 | HTML_DYNAMIC_SECTIONS = NO 157 | HTML_INDEX_NUM_ENTRIES = 100 158 | GENERATE_DOCSET = NO 159 | DOCSET_FEEDNAME = "Doxygen generated docs" 160 | DOCSET_BUNDLE_ID = org.doxygen.Project 161 | DOCSET_PUBLISHER_ID = org.doxygen.Publisher 162 | DOCSET_PUBLISHER_NAME = Publisher 163 | GENERATE_HTMLHELP = NO 164 | CHM_FILE = 165 | HHC_LOCATION = 166 | GENERATE_CHI = NO 167 | CHM_INDEX_ENCODING = 168 | BINARY_TOC = NO 169 | TOC_EXPAND = NO 170 | GENERATE_QHP = NO 171 | QCH_FILE = 172 | QHP_NAMESPACE = org.doxygen.Project 173 | QHP_VIRTUAL_FOLDER = doc 174 | QHP_CUST_FILTER_NAME = 175 | QHP_CUST_FILTER_ATTRS = 176 | QHP_SECT_FILTER_ATTRS = 177 | QHG_LOCATION = 178 | GENERATE_ECLIPSEHELP = NO 179 | ECLIPSE_DOC_ID = org.doxygen.Project 180 | DISABLE_INDEX = NO 181 | GENERATE_TREEVIEW = NO 182 | ENUM_VALUES_PER_LINE = 4 183 | TREEVIEW_WIDTH = 250 184 | EXT_LINKS_IN_WINDOW = NO 185 | FORMULA_FONTSIZE = 10 186 | FORMULA_TRANSPARENT = YES 187 | USE_MATHJAX = NO 188 | MATHJAX_FORMAT = HTML-CSS 189 | MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest 190 | MATHJAX_EXTENSIONS = 191 | MATHJAX_CODEFILE = 192 | SEARCHENGINE = YES 193 | SERVER_BASED_SEARCH = NO 194 | EXTERNAL_SEARCH = NO 195 | SEARCHENGINE_URL = 196 | SEARCHDATA_FILE = searchdata.xml 197 | EXTERNAL_SEARCH_ID = 198 | EXTRA_SEARCH_MAPPINGS = 199 | #--------------------------------------------------------------------------- 200 | # Configuration options related to the LaTeX output 201 | #--------------------------------------------------------------------------- 202 | GENERATE_LATEX = YES 203 | LATEX_OUTPUT = latex 204 | LATEX_CMD_NAME = latex 205 | MAKEINDEX_CMD_NAME = makeindex 206 | COMPACT_LATEX = NO 207 | PAPER_TYPE = a4 208 | EXTRA_PACKAGES = 209 | LATEX_HEADER = 210 | LATEX_FOOTER = 211 | LATEX_EXTRA_STYLESHEET = 212 | LATEX_EXTRA_FILES = 213 | PDF_HYPERLINKS = YES 214 | USE_PDFLATEX = YES 215 | LATEX_BATCHMODE = NO 216 | LATEX_HIDE_INDICES = NO 217 | LATEX_SOURCE_CODE = NO 218 | LATEX_BIB_STYLE = plain 219 | LATEX_TIMESTAMP = NO 220 | #--------------------------------------------------------------------------- 221 | # Configuration options related to the RTF output 222 | #--------------------------------------------------------------------------- 223 | GENERATE_RTF = NO 224 | RTF_OUTPUT = rtf 225 | COMPACT_RTF = NO 226 | RTF_HYPERLINKS = NO 227 | RTF_STYLESHEET_FILE = 228 | RTF_EXTENSIONS_FILE = 229 | RTF_SOURCE_CODE = NO 230 | #--------------------------------------------------------------------------- 231 | # Configuration options related to the man page output 232 | #--------------------------------------------------------------------------- 233 | GENERATE_MAN = NO 234 | MAN_OUTPUT = man 235 | MAN_EXTENSION = .3 236 | MAN_SUBDIR = 237 | MAN_LINKS = NO 238 | #--------------------------------------------------------------------------- 239 | # Configuration options related to the XML output 240 | #--------------------------------------------------------------------------- 241 | GENERATE_XML = NO 242 | XML_OUTPUT = xml 243 | XML_PROGRAMLISTING = YES 244 | #--------------------------------------------------------------------------- 245 | # Configuration options related to the DOCBOOK output 246 | #--------------------------------------------------------------------------- 247 | GENERATE_DOCBOOK = NO 248 | DOCBOOK_OUTPUT = docbook 249 | DOCBOOK_PROGRAMLISTING = NO 250 | #--------------------------------------------------------------------------- 251 | # Configuration options for the AutoGen Definitions output 252 | #--------------------------------------------------------------------------- 253 | GENERATE_AUTOGEN_DEF = NO 254 | #--------------------------------------------------------------------------- 255 | # Configuration options related to the Perl module output 256 | #--------------------------------------------------------------------------- 257 | GENERATE_PERLMOD = NO 258 | PERLMOD_LATEX = NO 259 | PERLMOD_PRETTY = YES 260 | PERLMOD_MAKEVAR_PREFIX = 261 | #--------------------------------------------------------------------------- 262 | # Configuration options related to the preprocessor 263 | #--------------------------------------------------------------------------- 264 | ENABLE_PREPROCESSING = YES 265 | MACRO_EXPANSION = NO 266 | EXPAND_ONLY_PREDEF = NO 267 | SEARCH_INCLUDES = YES 268 | INCLUDE_PATH = 269 | INCLUDE_FILE_PATTERNS = 270 | PREDEFINED = 271 | EXPAND_AS_DEFINED = 272 | SKIP_FUNCTION_MACROS = YES 273 | #--------------------------------------------------------------------------- 274 | # Configuration options related to external references 275 | #--------------------------------------------------------------------------- 276 | TAGFILES = 277 | GENERATE_TAGFILE = 278 | ALLEXTERNALS = NO 279 | EXTERNAL_GROUPS = YES 280 | EXTERNAL_PAGES = YES 281 | PERL_PATH = /usr/bin/perl 282 | #--------------------------------------------------------------------------- 283 | # Configuration options related to the dot tool 284 | #--------------------------------------------------------------------------- 285 | CLASS_DIAGRAMS = YES 286 | MSCGEN_PATH = 287 | DIA_PATH = 288 | HIDE_UNDOC_RELATIONS = YES 289 | HAVE_DOT = YES 290 | DOT_NUM_THREADS = 0 291 | DOT_FONTNAME = Helvetica 292 | DOT_FONTSIZE = 10 293 | DOT_FONTPATH = 294 | CLASS_GRAPH = YES 295 | COLLABORATION_GRAPH = YES 296 | GROUP_GRAPHS = YES 297 | UML_LOOK = NO 298 | UML_LIMIT_NUM_FIELDS = 10 299 | TEMPLATE_RELATIONS = NO 300 | INCLUDE_GRAPH = YES 301 | INCLUDED_BY_GRAPH = YES 302 | CALL_GRAPH = NO 303 | CALLER_GRAPH = NO 304 | GRAPHICAL_HIERARCHY = YES 305 | DIRECTORY_GRAPH = YES 306 | DOT_IMAGE_FORMAT = png 307 | INTERACTIVE_SVG = NO 308 | DOT_PATH = 309 | DOTFILE_DIRS = 310 | MSCFILE_DIRS = 311 | DIAFILE_DIRS = 312 | PLANTUML_JAR_PATH = 313 | PLANTUML_INCLUDE_PATH = 314 | DOT_GRAPH_MAX_NODES = 50 315 | MAX_DOT_GRAPH_DEPTH = 0 316 | DOT_TRANSPARENT = NO 317 | DOT_MULTI_TARGETS = NO 318 | GENERATE_LEGEND = YES 319 | DOT_CLEANUP = YES 320 | --------------------------------------------------------------------------------