Skip to content

Commit 3c929be

Browse files
authored
Merge pull request #35 from gbmhunter/develop
Reads no longer clear containers. Local testing improvements.
2 parents 71e8887 + ae0633d commit 3c929be

File tree

8 files changed

+131
-57
lines changed

8 files changed

+131
-57
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ cmake-build-debug/
77
docs/_build/
88

99
# Build artifacts
10-
a.out
10+
a.out
11+
test/arduino/test

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
77

8+
## [v2.7.0] - 2023-02-13
9+
10+
- `Read()` and `ReadBinary()` now append to the provided data containers (string or vector) rather than erase and write.
11+
- 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.
12+
813
## [v2.6.0] - 2023-02-02
914

1015
- `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).

README.md

+24-15
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,19 @@ int main() {
7171
// and no flow control
7272
SerialPort serialPort("/dev/ttyUSB0", BaudRate::B_57600, NumDataBits::EIGHT, Parity::NONE, NumStopBits::ONE);
7373
// Use SerialPort serialPort("/dev/ttyACM0", 13000); instead if you want to provide a custom baud rate
74-
serialPort.SetTimeout(-1); // Block when reading until any data is received
74+
serialPort.SetTimeout(100); // Block for up to 100ms to receive data
7575
serialPort.Open();
7676

77+
// WARNING: If using the Arduino Uno or similar, you may want to delay here, as opening the serial port causes
78+
// the micro to reset!
79+
7780
// Write some ASCII data
7881
serialPort.Write("Hello");
7982

80-
// Read some data back (will block until at least 1 byte is received due to the SetTimeout(-1) call above)
83+
// Read some data back (will block for up to 100ms due to the SetTimeout(100) call above)
8184
std::string readData;
8285
serialPort.Read(readData);
86+
std::cout << "Read data = \"" << readData << "\"" << std::endl;
8387

8488
// Close the serial port
8589
serialPort.Close();
@@ -153,37 +157,42 @@ sudo chmod 666 /dev/ttyACM0
153157

154158
## Tests
155159

160+
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).
161+
156162
### Prerequisties
157163

158-
Install arduino:avr platform:
164+
You will need:
165+
166+
* Arduino Uno (or equivalent) dev kit.
167+
* Linux OS.
168+
169+
Install the arduino-cli as per https://arduino.github.io/arduino-cli/0.21/installation/ on your local Linux machine.
170+
171+
Install the `arduino:avr` platform:
159172

160173
```
161174
$ arduino-cli core install arduino:avr
162175
```
163176

164-
Detect attached Arduino boards with:
177+
Make sure Arduino board is detected with:
165178

166179
```
167180
$ arduino-cli board list
168181
```
169182

170-
Compile:
171-
172-
```
173-
arduino-cli compile --fqbn arduino:avr:uno Basic/
174-
```
183+
### Running
175184

176-
Upload:
185+
Run the following bash script:
177186

178187
```
179-
arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:uno Basic/
188+
./test/arduino/run.sh
180189
```
181190

182-
### Running
191+
This script will:
183192

184-
```
185-
g++ main.cpp -lCppLinuxSerial
186-
```
193+
1. Build and install `CppLinuxSerial` onto your local Linux OS.
194+
1. Build and upload the test Arduino firmware to the connected Arduino Uno (it assumes it's connected to `/dev/ttyACM0`).
195+
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.
187196

188197
## Changelog
189198

include/CppLinuxSerial/SerialPort.hpp

+5-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/// \file SerialPort.hpp
33
/// \author Geoffrey Hunter <[email protected]> (www.mbedded.ninja)
44
/// \created 2014-01-07
5-
/// \last-modified 2022-11-12
5+
/// \last-modified 2023-02-13
66
/// \brief The main serial port class.
77
/// \details
88
/// See README.rst in repo root dir for more info.
@@ -171,20 +171,16 @@ namespace mn {
171171
/// \throws CppLinuxSerial::Exception if state != OPEN.
172172
void WriteBinary(const std::vector<uint8_t>& data);
173173

174-
/// \brief Use to read text from the COM port.
175-
/// \param data The object the read characters from the COM port will be saved to.
176-
/// \param wait_ms The amount of time to wait for data. Set to 0 for non-blocking mode. Set to -1
177-
/// to wait indefinitely for new data.
174+
/// \brief Use to read text from the COM port. Blocking nature depends on SetTimeout().
175+
/// \param data The read characters from the COM port will be appended to this string.
178176
/// \note Use ReadBinary() if you want to interpret received data as binary.
179177
/// \throws
180178
/// CppLinuxSerial::Exception if state != OPEN.
181179
/// std::system_error() if device has been disconnected.
182180
void Read(std::string& data);
183181

184-
/// \brief Use to read binary data from the COM port.
185-
/// \param data The object the read uint8_t bytes from the COM port will be saved to.
186-
/// \param wait_ms The amount of time to wait for data. Set to 0 for non-blocking mode. Set to -1
187-
/// to wait indefinitely for new data.
182+
/// \brief Use to read binary data from the COM port. Blocking nature depends on SetTimeout().
183+
/// \param data The read bytes from the COM port will be appended to this vector.
188184
/// \note Use Read() if you want to interpret received data as a string.
189185
/// \throws CppLinuxSerial::Exception if state != OPEN.
190186
/// std::system_error() if device has been disconnected.

src/SerialPort.cpp

+7-15
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! @file SerialPort.cpp
33
//! @author Geoffrey Hunter <[email protected]> (www.mbedded.ninja)
44
//! @created 2014-01-07
5-
//! @last-modified 2022-11-11
5+
//! @last-modified 2023-02-13
66
//! @brief The main serial port class.
77
//! @details
88
//! See README.rst in repo root dir for more info.
@@ -517,10 +517,7 @@ namespace CppLinuxSerial {
517517
}
518518
}
519519

520-
void SerialPort::Read(std::string& data)
521-
{
522-
data.clear();
523-
520+
void SerialPort::Read(std::string& data) {
524521
if(fileDesc_ == 0) {
525522
//this->sp->PrintError(SmartPrint::Ss() << "Read() was called but file descriptor (fileDesc) was 0, indicating file has not been opened.");
526523
//return false;
@@ -548,16 +545,13 @@ namespace CppLinuxSerial {
548545
}
549546
}
550547
else if(n > 0) {
551-
data = std::string(&readBuffer_[0], n);
548+
data += std::string(&readBuffer_[0], n);
552549
}
553550

554551
// If code reaches here, read must of been successful
555552
}
556553

557-
void SerialPort::ReadBinary(std::vector<uint8_t>& data)
558-
{
559-
data.clear();
560-
554+
void SerialPort::ReadBinary(std::vector<uint8_t>& data) {
561555
if(fileDesc_ == 0) {
562556
//this->sp->PrintError(SmartPrint::Ss() << "Read() was called but file descriptor (fileDesc) was 0, indicating file has not been opened.");
563557
//return false;
@@ -574,18 +568,16 @@ namespace CppLinuxSerial {
574568
if(n < 0) {
575569
// Read was unsuccessful
576570
throw std::system_error(EFAULT, std::system_category());
577-
}
578-
else if(n == 0) {
571+
} else if(n == 0) {
579572
// n == 0 means EOS, but also returned on device disconnection. We try to get termios2 to distinguish two these two states
580573
struct termios2 term2;
581574
int rv = ioctl(fileDesc_, TCGETS2, &term2);
582575

583576
if(rv != 0) {
584577
throw std::system_error(EFAULT, std::system_category());
585578
}
586-
}
587-
else if(n > 0) {
588-
copy(readBuffer_.begin(), readBuffer_.begin() + n, back_inserter(data));
579+
} else if(n > 0) {
580+
std::copy(readBuffer_.begin(), readBuffer_.begin() + n, back_inserter(data));
589581
}
590582

591583
// If code reaches here, read must of been successful

test/arduino/Basic/Basic.ino

+6-4
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@
1313
// the setup routine runs once when you press reset:
1414
void setup() {
1515
// initialize serial communication at 9600 bits per second:
16-
// Serial.begin(9600);
16+
Serial.begin(9600);
1717
// Serial.begin(9600, SERIAL_5N1); // 5 data bits, no parity, 1 stop bit
18-
Serial.begin(9600, SERIAL_8E2); // 8 data bits, even parity, 2 stop bits
18+
// Serial.begin(9600, SERIAL_8E2); // 8 data bits, even parity, 2 stop bits
1919
// Serial.begin(81234); // Used to test custom baud rates
2020
}
2121

2222
// the loop routine runs over and over again forever:
2323
void loop() {
24-
Serial.println("Hello");
25-
delay(100); // delay in between reads for stability
24+
if (Serial.available() > 0) {
25+
char c = Serial.read();
26+
Serial.print(c);
27+
}
2628
}

test/arduino/main.cpp

+62-13
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,89 @@
1+
#include <chrono>
2+
#include <thread>
3+
14
#include <CppLinuxSerial/SerialPort.hpp>
25

36
using namespace mn::CppLinuxSerial;
47

58
// Overload to be able to print vectors to std::cout
69
template <typename T>
710
std::ostream& operator<<( std::ostream& ostrm, const std::vector<T>& vec ){
11+
if (vec.size() == 0) {
12+
return ostrm << "[]";
13+
}
814
for( int j = 0, n = vec.size(); j < n; ++j ){
915
ostrm << ",["[ !j ] << " " << vec[ j ];
1016
}
1117
return ostrm << " ]";
1218
}
1319

20+
const double RX_TIMEOUT_MS = 2000;
21+
1422
int main() {
1523
// Create serial port object and open serial port
1624
SerialPort serialPort("/dev/ttyACM0", BaudRate::B_9600, NumDataBits::EIGHT, Parity::NONE, NumStopBits::ONE);
1725
// SerialPort serialPort("/dev/ttyACM0", 13000);
18-
serialPort.SetTimeout(-1); // Block when reading until any data is received
26+
27+
// Block for at most 100ms when receiving data
28+
// NOTE: I haven't had luck setting this to -1, Read() seems to never return
29+
// even though serial data has been sent to Linux
30+
serialPort.SetTimeout(100);
31+
32+
std::cout << "Opening /dev/ttyACM0 at 9600 baud, 8n1..." << std::flush;
1933
serialPort.Open();
34+
std::cout << "OK." << std::endl;
2035

21-
// Write some ASCII datae
22-
// serialPort0.Write("Hello");
36+
std::cout << "Sleeping to allow Arduino to restart (happens after opening serial port)..." << std::flush;
37+
std::this_thread::sleep_for(std::chrono::seconds(3));
38+
std::cout << "OK." << std::endl;
2339

24-
// Read some data back
25-
// while(1) {
26-
// std::string readData;
27-
// serialPort.Read(readData);
28-
// std::cout << "Received data: " << readData;
29-
// }
40+
// Write some ASCII data and read it back
41+
std::cout << "Writing string data..." << std::flush;
42+
std::string txDataString = "Hello";
43+
serialPort.Write(txDataString);
44+
std:: cout << "OK." << std::endl;
45+
46+
std::cout << "Reading string data..." << std::flush;
47+
std::string rxDataString;
48+
auto t_start = std::chrono::high_resolution_clock::now();
49+
while(1) {
50+
serialPort.Read(rxDataString);
51+
if (rxDataString == txDataString) {
52+
std::cout << "OK." << std::endl;
53+
break;
54+
}
55+
auto t_now = std::chrono::high_resolution_clock::now();
56+
double elapsed_time_ms = std::chrono::duration<double, std::milli>(t_now - t_start).count();
57+
if (elapsed_time_ms >= RX_TIMEOUT_MS) {
58+
std::cout << "ERROR: Did not receive the string data \"Hello\" from Arduino." << std::endl;
59+
return -1;
60+
}
61+
}
3062

31-
// Read some data back (raw)
63+
// Write some binary data and read it back
64+
std::cout << "Writing binary data..." << std::flush;
65+
std::vector<uint8_t> txDataBinary{ 1, 2, 3, 4, 5 };
66+
serialPort.WriteBinary(txDataBinary);
67+
std:: cout << "OK." << std::endl;
68+
69+
std::cout << "Reading binary data..." << std::flush;
70+
std::vector<uint8_t> rxDataBinary;
71+
t_start = std::chrono::high_resolution_clock::now();
3272
while(1) {
33-
std::vector<unsigned char> readData;
34-
serialPort.ReadBinary(readData);
35-
std::cout << "Received data: " << readData;
73+
serialPort.ReadBinary(rxDataBinary);
74+
if (rxDataBinary == txDataBinary) {
75+
std::cout << "OK." << std::endl;
76+
break;
77+
}
78+
auto t_now = std::chrono::high_resolution_clock::now();
79+
double elapsed_time_ms = std::chrono::duration<double, std::milli>(t_now - t_start).count();
80+
if (elapsed_time_ms >= RX_TIMEOUT_MS) {
81+
std::cout << "ERROR: Did not receive the binary data from Arduino." << std::endl;
82+
return -1;
83+
}
3684
}
3785

3886
// Close the serial port
3987
serialPort.Close();
88+
return 0;
4089
}

test/arduino/run.sh

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# exit when any command fails
2+
set -e
3+
4+
echo "Building/installing CppLinuxSerial..."
5+
cd build/
6+
cmake ..
7+
sudo make install
8+
9+
echo "Building test application..."
10+
cd ../test/arduino
11+
g++ main.cpp -lCppLinuxSerial -o test
12+
13+
echo "Compiling Arduino firmware..."
14+
arduino-cli compile --fqbn arduino:avr:uno ./Basic/
15+
16+
echo "Uploading Arduino firmware..."
17+
arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:uno ./Basic/
18+
19+
echo "Running test application..."
20+
./test

0 commit comments

Comments
 (0)