From ae0633da942c7e5531d2fc2d9ee1d879c6373043 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Mon, 13 Feb 2023 12:03:37 +1300 Subject: [PATCH] Added bash script to run local tests. Updated local tests to send and receive both string and binary data. --- .gitignore | 3 +- CHANGELOG.md | 5 ++ README.md | 39 ++++++++------ include/CppLinuxSerial/SerialPort.hpp | 14 ++--- src/SerialPort.cpp | 22 +++----- test/arduino/Basic/Basic.ino | 10 ++-- test/arduino/main.cpp | 75 ++++++++++++++++++++++----- test/arduino/run.sh | 20 +++++++ 8 files changed, 131 insertions(+), 57 deletions(-) create mode 100755 test/arduino/run.sh diff --git a/.gitignore b/.gitignore index a749124..0f0c608 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ cmake-build-debug/ docs/_build/ # Build artifacts -a.out \ No newline at end of file +a.out +test/arduino/test \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 03a6388..90f5d4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [v2.7.0] - 2023-02-13 + +- `Read()` and `ReadBinary()` now append to the provided data containers (string or vector) rather than erase and write. +- Added `run.sh` bash script for running local serial port tests with connected Arduino Uno (see README). Updated local tests to write and read back data in both string and binary forms. + ## [v2.6.0] - 2023-02-02 - `Read()` and `ReadBinary()` now throw exceptions if they detect that the serial device has been disconnected (thanks to [aldoshkind](https://github.com/aldoshkind) for helping with this one). diff --git a/README.md b/README.md index 8e31f60..7581073 100644 --- a/README.md +++ b/README.md @@ -71,15 +71,19 @@ int main() { // and no flow control SerialPort serialPort("/dev/ttyUSB0", BaudRate::B_57600, NumDataBits::EIGHT, Parity::NONE, NumStopBits::ONE); // Use SerialPort serialPort("/dev/ttyACM0", 13000); instead if you want to provide a custom baud rate - serialPort.SetTimeout(-1); // Block when reading until any data is received + serialPort.SetTimeout(100); // Block for up to 100ms to receive data serialPort.Open(); + // WARNING: If using the Arduino Uno or similar, you may want to delay here, as opening the serial port causes + // the micro to reset! + // Write some ASCII data serialPort.Write("Hello"); - // Read some data back (will block until at least 1 byte is received due to the SetTimeout(-1) call above) + // Read some data back (will block for up to 100ms due to the SetTimeout(100) call above) std::string readData; serialPort.Read(readData); + std::cout << "Read data = \"" << readData << "\"" << std::endl; // Close the serial port serialPort.Close(); @@ -153,37 +157,42 @@ sudo chmod 666 /dev/ttyACM0 ## Tests +Serial port testing cannot really be done easily on cloud-based CICD platforms, as serial ports and devices connected to these ports are not readibly available (nor configurable). `CppLinuxSerial` relies on running tests manually on your local Linux OS, alongside a connected Arduino Uno configured to echo serial data back (at a later data this could be reconfigured to cycle through tests at different baud rates, parity settings, e.t.c). + ### Prerequisties -Install arduino:avr platform: +You will need: + +* Arduino Uno (or equivalent) dev kit. +* Linux OS. + +Install the arduino-cli as per https://arduino.github.io/arduino-cli/0.21/installation/ on your local Linux machine. + +Install the `arduino:avr` platform: ``` $ arduino-cli core install arduino:avr ``` -Detect attached Arduino boards with: +Make sure Arduino board is detected with: ``` $ arduino-cli board list ``` -Compile: - -``` -arduino-cli compile --fqbn arduino:avr:uno Basic/ -``` +### Running -Upload: +Run the following bash script: ``` -arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:uno Basic/ +./test/arduino/run.sh ``` -### Running +This script will: -``` -g++ main.cpp -lCppLinuxSerial -``` +1. Build and install `CppLinuxSerial` onto your local Linux OS. +1. Build and upload the test Arduino firmware to the connected Arduino Uno (it assumes it's connected to `/dev/ttyACM0`). +1. Build and run the test C++ application. This sends serial data to the Uno via CppLinuxSerial and expects the data to be echoed back. ## Changelog diff --git a/include/CppLinuxSerial/SerialPort.hpp b/include/CppLinuxSerial/SerialPort.hpp index b2ce252..e1785ce 100644 --- a/include/CppLinuxSerial/SerialPort.hpp +++ b/include/CppLinuxSerial/SerialPort.hpp @@ -2,7 +2,7 @@ /// \file SerialPort.hpp /// \author Geoffrey Hunter (www.mbedded.ninja) /// \created 2014-01-07 -/// \last-modified 2022-11-12 +/// \last-modified 2023-02-13 /// \brief The main serial port class. /// \details /// See README.rst in repo root dir for more info. @@ -171,20 +171,16 @@ namespace mn { /// \throws CppLinuxSerial::Exception if state != OPEN. void WriteBinary(const std::vector& data); - /// \brief Use to read text from the COM port. - /// \param data The object the read characters from the COM port will be saved to. - /// \param wait_ms The amount of time to wait for data. Set to 0 for non-blocking mode. Set to -1 - /// to wait indefinitely for new data. + /// \brief Use to read text from the COM port. Blocking nature depends on SetTimeout(). + /// \param data The read characters from the COM port will be appended to this string. /// \note Use ReadBinary() if you want to interpret received data as binary. /// \throws /// CppLinuxSerial::Exception if state != OPEN. /// std::system_error() if device has been disconnected. void Read(std::string& data); - /// \brief Use to read binary data from the COM port. - /// \param data The object the read uint8_t bytes from the COM port will be saved to. - /// \param wait_ms The amount of time to wait for data. Set to 0 for non-blocking mode. Set to -1 - /// to wait indefinitely for new data. + /// \brief Use to read binary data from the COM port. Blocking nature depends on SetTimeout(). + /// \param data The read bytes from the COM port will be appended to this vector. /// \note Use Read() if you want to interpret received data as a string. /// \throws CppLinuxSerial::Exception if state != OPEN. /// std::system_error() if device has been disconnected. diff --git a/src/SerialPort.cpp b/src/SerialPort.cpp index f8aa2b7..4cd4a56 100644 --- a/src/SerialPort.cpp +++ b/src/SerialPort.cpp @@ -2,7 +2,7 @@ //! @file SerialPort.cpp //! @author Geoffrey Hunter (www.mbedded.ninja) //! @created 2014-01-07 -//! @last-modified 2022-11-11 +//! @last-modified 2023-02-13 //! @brief The main serial port class. //! @details //! See README.rst in repo root dir for more info. @@ -517,10 +517,7 @@ namespace CppLinuxSerial { } } - void SerialPort::Read(std::string& data) - { - data.clear(); - + void SerialPort::Read(std::string& data) { if(fileDesc_ == 0) { //this->sp->PrintError(SmartPrint::Ss() << "Read() was called but file descriptor (fileDesc) was 0, indicating file has not been opened."); //return false; @@ -548,16 +545,13 @@ namespace CppLinuxSerial { } } else if(n > 0) { - data = std::string(&readBuffer_[0], n); + data += std::string(&readBuffer_[0], n); } // If code reaches here, read must of been successful } - void SerialPort::ReadBinary(std::vector& data) - { - data.clear(); - + void SerialPort::ReadBinary(std::vector& data) { if(fileDesc_ == 0) { //this->sp->PrintError(SmartPrint::Ss() << "Read() was called but file descriptor (fileDesc) was 0, indicating file has not been opened."); //return false; @@ -574,8 +568,7 @@ namespace CppLinuxSerial { if(n < 0) { // Read was unsuccessful throw std::system_error(EFAULT, std::system_category()); - } - else if(n == 0) { + } else if(n == 0) { // n == 0 means EOS, but also returned on device disconnection. We try to get termios2 to distinguish two these two states struct termios2 term2; int rv = ioctl(fileDesc_, TCGETS2, &term2); @@ -583,9 +576,8 @@ namespace CppLinuxSerial { if(rv != 0) { throw std::system_error(EFAULT, std::system_category()); } - } - else if(n > 0) { - copy(readBuffer_.begin(), readBuffer_.begin() + n, back_inserter(data)); + } else if(n > 0) { + std::copy(readBuffer_.begin(), readBuffer_.begin() + n, back_inserter(data)); } // If code reaches here, read must of been successful diff --git a/test/arduino/Basic/Basic.ino b/test/arduino/Basic/Basic.ino index 044bd1e..3dc1828 100644 --- a/test/arduino/Basic/Basic.ino +++ b/test/arduino/Basic/Basic.ino @@ -13,14 +13,16 @@ // the setup routine runs once when you press reset: void setup() { // initialize serial communication at 9600 bits per second: - // Serial.begin(9600); + Serial.begin(9600); // Serial.begin(9600, SERIAL_5N1); // 5 data bits, no parity, 1 stop bit - Serial.begin(9600, SERIAL_8E2); // 8 data bits, even parity, 2 stop bits + // Serial.begin(9600, SERIAL_8E2); // 8 data bits, even parity, 2 stop bits // Serial.begin(81234); // Used to test custom baud rates } // the loop routine runs over and over again forever: void loop() { - Serial.println("Hello"); - delay(100); // delay in between reads for stability + if (Serial.available() > 0) { + char c = Serial.read(); + Serial.print(c); + } } diff --git a/test/arduino/main.cpp b/test/arduino/main.cpp index 3a3352c..dbf20e7 100644 --- a/test/arduino/main.cpp +++ b/test/arduino/main.cpp @@ -1,3 +1,6 @@ +#include +#include + #include using namespace mn::CppLinuxSerial; @@ -5,36 +8,82 @@ using namespace mn::CppLinuxSerial; // Overload to be able to print vectors to std::cout template std::ostream& operator<<( std::ostream& ostrm, const std::vector& vec ){ + if (vec.size() == 0) { + return ostrm << "[]"; + } for( int j = 0, n = vec.size(); j < n; ++j ){ ostrm << ",["[ !j ] << " " << vec[ j ]; } return ostrm << " ]"; } +const double RX_TIMEOUT_MS = 2000; + int main() { // Create serial port object and open serial port SerialPort serialPort("/dev/ttyACM0", BaudRate::B_9600, NumDataBits::EIGHT, Parity::NONE, NumStopBits::ONE); // SerialPort serialPort("/dev/ttyACM0", 13000); - serialPort.SetTimeout(-1); // Block when reading until any data is received + + // Block for at most 100ms when receiving data + // NOTE: I haven't had luck setting this to -1, Read() seems to never return + // even though serial data has been sent to Linux + serialPort.SetTimeout(100); + + std::cout << "Opening /dev/ttyACM0 at 9600 baud, 8n1..." << std::flush; serialPort.Open(); + std::cout << "OK." << std::endl; - // Write some ASCII datae - // serialPort0.Write("Hello"); + std::cout << "Sleeping to allow Arduino to restart (happens after opening serial port)..." << std::flush; + std::this_thread::sleep_for(std::chrono::seconds(3)); + std::cout << "OK." << std::endl; - // Read some data back - // while(1) { - // std::string readData; - // serialPort.Read(readData); - // std::cout << "Received data: " << readData; - // } + // Write some ASCII data and read it back + std::cout << "Writing string data..." << std::flush; + std::string txDataString = "Hello"; + serialPort.Write(txDataString); + std:: cout << "OK." << std::endl; + + std::cout << "Reading string data..." << std::flush; + std::string rxDataString; + auto t_start = std::chrono::high_resolution_clock::now(); + while(1) { + serialPort.Read(rxDataString); + if (rxDataString == txDataString) { + std::cout << "OK." << std::endl; + break; + } + auto t_now = std::chrono::high_resolution_clock::now(); + double elapsed_time_ms = std::chrono::duration(t_now - t_start).count(); + if (elapsed_time_ms >= RX_TIMEOUT_MS) { + std::cout << "ERROR: Did not receive the string data \"Hello\" from Arduino." << std::endl; + return -1; + } + } - // Read some data back (raw) + // Write some binary data and read it back + std::cout << "Writing binary data..." << std::flush; + std::vector txDataBinary{ 1, 2, 3, 4, 5 }; + serialPort.WriteBinary(txDataBinary); + std:: cout << "OK." << std::endl; + + std::cout << "Reading binary data..." << std::flush; + std::vector rxDataBinary; + t_start = std::chrono::high_resolution_clock::now(); while(1) { - std::vector readData; - serialPort.ReadBinary(readData); - std::cout << "Received data: " << readData; + serialPort.ReadBinary(rxDataBinary); + if (rxDataBinary == txDataBinary) { + std::cout << "OK." << std::endl; + break; + } + auto t_now = std::chrono::high_resolution_clock::now(); + double elapsed_time_ms = std::chrono::duration(t_now - t_start).count(); + if (elapsed_time_ms >= RX_TIMEOUT_MS) { + std::cout << "ERROR: Did not receive the binary data from Arduino." << std::endl; + return -1; + } } // Close the serial port serialPort.Close(); + return 0; } \ No newline at end of file diff --git a/test/arduino/run.sh b/test/arduino/run.sh new file mode 100755 index 0000000..6ec6dce --- /dev/null +++ b/test/arduino/run.sh @@ -0,0 +1,20 @@ +# exit when any command fails +set -e + +echo "Building/installing CppLinuxSerial..." +cd build/ +cmake .. +sudo make install + +echo "Building test application..." +cd ../test/arduino +g++ main.cpp -lCppLinuxSerial -o test + +echo "Compiling Arduino firmware..." +arduino-cli compile --fqbn arduino:avr:uno ./Basic/ + +echo "Uploading Arduino firmware..." +arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:uno ./Basic/ + +echo "Running test application..." +./test