'Read from USB until a special character ; with Ubuntu and Arduino

I have an Arduino code that outputs strings coming from some sensors over the USB. These are the strings:

Sensor 1 F1 410nm : 0 F2 435nm : 290 F3 445nm : 0 F4 460nm : 211 F5 480nm : 0 F6 515nm : 0 F7 535nm : 262 F8 555nm : 0 F9 590nm : 0 F10 610nm : 263 F11 630nm : 0 F12 645nm : 262 F13 680nm : 0 F14 715nm : 181 F15 730nm : 201 F16 780nm : 0 F17  860nm  : 262 F18 940nm : 211;
Sensor 0 F1 410nm : 0 F2 435nm : 171 F3 445nm : 0 F4 460nm : 213 F5 480nm : 0 F6 515nm : 0 F7 535nm : 228 F8 555nm : 0 F9 590nm : 0 F10 610nm : 150 F11 630nm : 0 F12 645nm : 200 F13 680nm : 0 F14 715nm : 159 F15 730nm : 204 F16 780nm : 0 F17  860nm  : 213 F18 940nm : 228;

Each string is terminated by the character ";".

Now, I wrote a simple C++ program that runs on Ubuntu and reads from serial and outputs data. Arduino is connected via USB to the computer where I have to run the C/C++ program. The problem is that it prints characters are they arrive and randomly, like this:

Sensor 0 F1 410nm : 0 F2 435nm : 272 F3 445nm : [ INFO] [1652193464.896782509]: Sensor 0 F1 410nm : 0 F2 435nm : 272 F3 445nm : 0 F4 460nm : 178 F5 480nm : 0 F6 515nm : 0 F7 53[ INFO] [1652193464.900888273]: 0 F4 460nm : 178 F5 480nm : 0 F6 515nm : 0 F7 53 5nm : 298 F8 555nm : 0 F9 590nm : 0 F10 610nm : 170 F11 630nm : 0 F12 645nm : 237 F13 680nm : 0 F14 715nm : 297 F15 730nm : 289 F16 780nm : 0 F17 860nm : 178 F18 940nm : 298;

while I would like to read all the characters till the ";" and then print the full string.

How can I do this in a smart way?

I can also format the string from Arduino by adding a character before each string, for example:

:String1_with_values;

in order to be able to know when the string starts and when it finishes. Do you think it would be a good solution to improve it?

This is my current full code:

#include "ros/ros.h"
#include "std_msgs/String.h"
// C library headers
#include <iostream> 
#include <stdio.h>
#include <string.h>

// Linux headers
#include <fcntl.h> // Contains file controls like O_RDWR
#include <errno.h> // Error integer and strerror() function
#include <termios.h> // Contains POSIX terminal control definitions
#include <unistd.h> // write(), read(), close()

int main(int argc,char **argv) {

 ros::init(argc, argv, "talker");
 ros::NodeHandle n;
 ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

  ros::Rate loop_rate(10);

  // Open the serial port. Change device path as needed (currently set to an standard FTDI USB-UART cable type device)
  int serial_port = open("/dev/ttyACM0", O_RDWR);

  // Create new termios struc, we call it 'tty' for convention
  struct termios tty;

  // Read in existing settings, and handle any error
  if(tcgetattr(serial_port, &tty) != 0) {
      printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
      return 1;
  }

  tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
  tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
  tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size 
  tty.c_cflag |= CS8; // 8 bits per byte (most common)
  tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
  tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)

  tty.c_lflag &= ~ICANON;
  tty.c_lflag &= ~ECHO; // Disable echo
  tty.c_lflag &= ~ECHOE; // Disable erasure
  tty.c_lflag &= ~ECHONL; // Disable new-line echo
  tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
  tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
  tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes

  tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
  tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
  // tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT ON LINUX)
  // tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT ON LINUX)

  tty.c_cc[VTIME] = 10;    // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.
  tty.c_cc[VMIN] = 0;

  // Set in/out baud rate to be 9600
  cfsetispeed(&tty, B115200);
  cfsetospeed(&tty, B115200);

  // Save tty settings, also checking for error
  if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
      printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
      return 1;
  }
unsigned char read_buf[800];
int count = 0;
 while (ros::ok())
  {
    int n = read(serial_port, read_buf, sizeof(read_buf) - 1);
    if (n < 0) {
        /* handle errno condition */
        return -1;
    }
    read_buf[n] = '\0';
    std::cout << read_buf ;

std_msgs::String msg;

    std::stringstream ss;
    ss << read_buf;
    msg.data = ss.str();

    ROS_INFO("%s", msg.data.c_str());

    chatter_pub.publish(msg);

    ros::spinOnce();

    loop_rate.sleep();
    ++count;

}

  close(serial_port);
  return 0; // success
}


Solution 1:[1]

The comment on your source says:

"// Wait for up to 1s (10 deciseconds), returning as soon as any data is received."

Since a transmission over a serial line takes some time, you will in most cases receive the message in multiple parts. (Look up "baudrate" to learn about transmission times.) How many characters one part contains, depends on many factors, and commonly you cannot foresee it.

Change your reading algorithm to a simple finite state machine that collects received characters until the end-of-message marker is received. A primitive solution would scan for it.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 the busybee