Unreliable UART communication in C++ - c++

I have written a small code in C++ that reads the data in serial port and sometimes writes to the serial port when the callback is being called. I am using this library in C++ for the serial communication:
http://wjwwood.io/serial/doc/1.1.0/index.html
The problem I have is that sometimes the serial port stops writing to the data (does not happen often but happens) and also I need to write twice to the serial port in order for the data to be changed in the serial. For debugging purposes, I have added outputs that shows the data that has been read and the data that has been written.
Here is my code:
void UART::run()
{
while(ros::ok())
{
_letter = _serial.read();
if (_serial.available() > 0)
{
_line.clear();
_line = _serial.readline(65536, "!");
_words.clear();
std::istringstream f(_line);
std::string s;
while (getline(f,s,'\t'))
{
_words.push_back(s);
}
this->fillVars();
_msg.velocity = _velocity;
_msg.position = _position;
_pub.publish(_msg);
ros::spinOnce();
}
_serial.flush();
}
}
void UART::fillVars()
{
if (_words[0] == "u")
{
_ultrasonic = std::stoi(_words[1]);
}
else if (_words[0] == "s")
{
std::cout << "reading " << _words[1] << std::endl;
}
}
void UART::callback(const std_msgs::Int32::ConstPtr& speed)
{
// Convert m/s and serial write
_effort = speed->data - 32;
std::string toWrite = std::to_string(_effort);
size_t temp = _serial.write(toWrite);
std::cout << "Writing :" << toWrite << std::endl;
}
The code always runs in the run() function and pauses when the callback happens.
The only data in the serial for now is:
!s 23
where 23 is any integer and I am simply writing to change the integer when callback is being called.
So what can be wrong with my code? Why do I need to call the callback twice in order to change the data in the serial and why does sometime the writing does not happen (my assumption is that the while loop gets stuck)?

Related

RtMidi MIDI control signals to Ableton Live

I'm trying to write a C++ app using RtMidi to send control signals via my Tascam FireOne MIDI Controller to Ableton Live. So far, I have it successfully sending Note On + Off Signals, Volume Up + Down Signals, etc. via my MIDI Controller to my digital piano using 'a' and 's' keypresses.
// midiout.cpp
#include <iostream>
using namespace std;
#include <signal.h>
#include <windows.h>
#include <conio.h>
#include "RtMidi.h"
int main()
{
std::vector<unsigned char> message;
int i, keyPress;
int nPorts;
char input;
RtMidiOut *midiout = 0;
// midiOUT
try {
midiout = new RtMidiOut();
// Check available ports.
nPorts = midiout->getPortCount();
if ( nPorts == 0 ) {
cout << "No ports available!" << endl;
goto cleanup;
}
// List Available Ports
cout << "\nPort Count = " << nPorts << endl;
cout << "Available Output Ports\n-----------------------------\n";
for( i=0; i<nPorts; i++ )
{
try {
cout << " Output Port Number " << i << " : " << midiout->getPortName(i) << endl;
}
catch(RtError &error) {
error.printMessage();
goto cleanup;
}
}
cout << "\nSelect an output port number :" << endl;
cin >> keyPress;
while( keyPress < 0 || keyPress >= midiout->getPortCount() )
{
cout << "\nIncorrect selection. Please try again :" << endl;
cin >> keyPress;
}
// Open Selected Port
midiout->openPort( keyPress );
keyPress = NULL;
bool done = false;
cout << "Press a key to generate a message, press 'Esc' to exit" << endl;
while(!done)
{
keyPress = _getch();
input = keyPress;
cout << input << " is: " << keyPress << endl;
switch ( keyPress )
{
case 97 :
// Process for keypress = a
// Note On: 144, 60, 90
message.push_back( 144 );
message.push_back( 60 );
message.push_back( 90 );
midiout->sendMessage( &message );
break;
case 115 :
// Process for keypress = s
// Note Off: 128, 60, 90
message.push_back( 128 );
message.push_back( 60 );
message.push_back( 90 );
midiout->sendMessage( &message );
break;
case 27 :
// Process for keypress = esc
done = true;
break;
}
message.clear();
keyPress = NULL;
}
}
catch(RtError &error) {
error.printMessage();
exit( EXIT_FAILURE );
}
cleanup:
delete midiout;
return 0;
}
I tried sending control signals in the same manner as above but this time with control values in the message bytes in place of the note-on or note-off values.
When ableton live is running, I press a key to send a signal but the app locks up and doesn't return to the start of the while loop to receive input from the next keypress.
edit: I've just noticed that even the above code (which usually runs fine) freezes when ableton live is running and I press a key.
further edit: I downloaded a really neat app called MIDI Monitor, which can monitor MIDI data being transferred: http://obds.free.fr/midimon -- my MIDI controller device has two ports -> one for MIDI and one for control. When I'm monitoring control, I can send midi signals and vice versa. However, if, for example, I'm monitoring control and I try to send some CC type data the program locks. Could this be a device driver problem? –
Does anyone know what is going wrong here?
Just one comment - your exception handling is a little weird.
I'd wrap the whole code (initialization and all) in a try/catch(RtError &err) block, and lose most of the other try/catch blocks.
In particular, I don't know what your catch(char * str) stuff will achieve, and you have no catch at all if openPort() throws.
First of all, try sending a different CC and mapping it to some arbitrary control in Ableton, just to see if it's working. The volume controls you are trying to alter behave slightly differently than regular CC's. Specifically, you should check out the MIDI organization's recommended practice for incrementing/decrementing controllers (note that 0x60 == 96), namely when they write:
This parameter uses MSB and LSB separately, with MSB adjusting sensitivity in semitones and LSB adjusting sensitivity in cents. Some manufacturers may even ignore adjustments to the LSB.

Sending a long String over a Socket C++

I'm having a strange issue with some basic client <-> server communication using sockets.
The server packages an array of structs containing player information by dumping the data into one long string using an ostringstream object and some some simple formatting with newlines and spaces, then sends that string via socket to the client to be displayed to the player.
The issue arises when the "name" field in the one or more of the structs being packaged is longer than 4 characters; if this is the case every character after the 4th from each "name" field containing 5+ characters is duplicated on a single line at the end of the string AFTER* being sent to the client.
*The packaged string always displays properly on the server side.
Below is the relevant code and two screenshots of server and client terminal, one displaying the error, the other without.
SERVER CODE:
struct leader_board_pos
{
string name;
int score;
};
leader_board_pos leader_board[num_players];
....
// Package and send the current leaderboard to the client
int i = 0;
string leader_board_package;
string dubspace = " ";
ostringstream oss;
cout << "Package leader board > "; cin.ignore();
leader_board_package = "Leader Board: \n";
while(i < num_leaders)
{
oss << leader_board[i].name << dubspace << leader_board[i].score << endl;
leader_board_package += oss.str();
oss.str(string());
i++;
}
cout << leader_board_package; cin.ignore();
bytes_sent = send(clientSock, leader_board_package.c_str(), leader_board_package.length(), 0);
if (bytes_sent != leader_board_package.length())
{
cout << "Server Message: Communication error..." << endl;
return;
}
CLIENT CODE:
const int MAX_BUFF_LENGTH = 4096;
...
//Get and display Leaderboard
int bytes_recv = 0;
vector<char> leader_board_buffer(MAX_BUFF_LENGTH);
string leader_board_package;
do {
bytes_recv = recv(sock, leader_board_buffer.data(), MAX_BUFF_LENGTH, 0);
if (bytes_recv == -1)
{
cout << "Communication error...";
return 0;
}
else
{
leader_board_package.append(leader_board_buffer.begin(), leader_board_buffer.end());
}
} while (bytes_recv == MAX_BUFF_LENGTH);
cout << endl << endl << leader_board_package; cin.ignore();
The Screenshots, Relevant sections highlighted:
Screenshot with Error
Screenshot without Error
Unless I'm missing something else I'm 99% sure the error is in the do-while receive loop on the client-side since the string displays appropriately when printed on the server-side. I can't for the love of me imagine what would cause such a specific error since it's just one long string being sent.
leader_board_package.append(leader_board_buffer.begin(), leader_board_buffer.end());
This is wrong. The buffer only contains bytes_recv worth of received data.
while (bytes_recv == MAX_BUFF_LENGTH);
If you assemble the message from pieces, you need to add their lengths together somewhere.
In addition,
if (bytes_sent != leader_board_package.length())
This is wrong too. Don't rely on send being able to send the entire array at once. The right way to send is to loop much in the same way you loop receiving.
It is also unclear what's the size of your message is supposed to be. If it's variable, you need to transmit it with the message. If it's MAX_BUFF_LENGTH, you need to send() this exact number of bytes. You cannot rely on a message being sent or received in one piece over TCP.

Having issues with sockets and telnet

I've been learning sockets, and I have created a basic server where you can telnet into and type messages, then press enter and the message is printed on the server.
Since it's telnet, every key press gets sent to the server. So I basically hold all sent bytes in a buffer, and then when a carriage return ("\r\n") is received, I discard that, and print out the clients current buffer. Then I clear the clients buffer.
My problem is that every once in a while (and I'm not quite sure how to replicate it), the first "line" of data I send in gets an extra space tacked onto each character. For example, I'll type "Test" on the telnet client, but my server will receive it as "T e s t ". I always clear the receiving buffer before receiving any data. One obvious solution is just to remove all spaces serverside, but then that messes up my ability to send more than one word. Is this just an issue with my telnet, or is there something I can do on the server to fix this?
I am using the WinSock2 API and Windows 10 Telnet.
EDIT:
I have checked the hex value of the extra character, and it is 0x20.
EDIT:
Here is the code that receives and handles the incoming telnet data.
// This client is trying to send some data to us
memset(receiveBuffer, sizeof(receiveBuffer), 0);
int receivedBytes = recv(client->socket, receiveBuffer, sizeof(receiveBuffer), 0);
if (receivedBytes == SOCKET_ERROR)
{
FD_CLR(client->socket, &masterFDSet);
std::cerr << "Error! recv(): " << WSAGetLastError() << std::endl;
closesocket(client->socket);
client->isDisconnected = true;
continue;
}
else if (receivedBytes == 0)
{
FD_CLR(client->socket, &masterFDSet);
std::cout << "Socket " << client->socket << " was closed by the client." << std::endl;
closesocket(client->socket);
client->isDisconnected = true;
continue;
}
// Print out the hex value of the incoming data, for debug purposes
const int siz_ar = strlen(receiveBuffer);
for (int i = 0; i < siz_ar; i++)
{
std::cout << std::hex << (int)receiveBuffer[i] << " " << std::dec;
}
std::cout << std::endl;
std::string stringCRLF = "\r\n"; // Carraige return representation
std::string stringBS = "\b"; // Backspace representation
std::string commandBuffer = receiveBuffer;
if (commandBuffer.find(stringCRLF) != std::string::npos)
{
// New line detected. Process message.
ProcessClientMessage(client);
}
else if (commandBuffer.find(stringBS) != std::string::npos)
{
// Backspace detected,
int size = strlen(client->dataBuffer);
client->dataBuffer[size - 1] = '\0';
}
else
{
// Strip any extra dumb characters that might have found their way in there
commandBuffer.erase(std::remove(commandBuffer.begin(), commandBuffer.end(), '\r'), commandBuffer.end());
commandBuffer.erase(std::remove(commandBuffer.begin(), commandBuffer.end(), '\n'), commandBuffer.end());
// Add the new data to the clients data buffer
strcat_s(client->dataBuffer, sizeof(client->dataBuffer), commandBuffer.c_str());
}
std::cout << "length of data buffer is " << strlen(client->dataBuffer) << std::endl;
You have two major problems.
First, you have a variable, receivedBytes that knows the number of bytes you received. Why then do you call strlen? You have no guarantee that the data you received is a C-style string. It could, for example, contain embedded zero bytes. Do not call strlen on it.
Second, you check the data you just received for a \r\n, rather than the full receive buffer. And you receive data into the beginning of the receive buffer, not the first unused space in it. As a result, if one call to recv gets the \r and the next gets the \n, your code will do the wrong thing.
You never actually wrote code to receive a message. You never actually created a message buffer to hold the received message.
Your code, my comments:
memset(receiveBuffer, sizeof(receiveBuffer), 0);
You don't need this. You shouldn't need this. If you do there is a bug later in your code.
int receivedBytes = recv(client->socket, receiveBuffer, sizeof(receiveBuffer), 0);
if (receivedBytes == SOCKET_ERROR)
{
FD_CLR(client->socket, &masterFDSet);
std::cerr << "Error! recv(): " << WSAGetLastError() << std::endl;
closesocket(client->socket);
client->isDisconnected = true;
continue;
You mean 'break'. You got an error. You closed the socket. There is nothing to continue.
}
else if (receivedBytes == 0)
{
FD_CLR(client->socket, &masterFDSet);
std::cout << "Socket " << client->socket << " was closed by the client." << std::endl;
closesocket(client->socket);
client->isDisconnected = true;
continue;
Ditto. You mean 'break'. You got an error. You closed the socket. There is nothing to continue.
}
// Print out the hex value of the incoming data, for debug purposes
const int siz_ar = strlen(receiveBuffer);
Bzzzzzzzzzzzzt. There is no guarantee there is a null anywhere in the buffer. You don't need this variable. The correct value is already present, in receivedBytes.
for (int i = 0; i < siz_ar; i++)
That should be `for (int i = 0; i < receivedBytes; i++)
{
std::cout << std::hex << (int)receiveBuffer[i] << " " << std::dec;
}
std::cout << std::endl;
std::string stringCRLF = "\r\n"; // Carraige return representation
No. That is a carriage return (\r) followed by a line feed (\n), often called CRLF as indeed you have yourself in the variable name. This is the standard line terminator in Telnet.
std::string stringBS = "\b"; // Backspace representation
std::string commandBuffer = receiveBuffer;
Bzzt. This copy should be length-delimited by receivedBytes.
if (commandBuffer.find(stringCRLF) != std::string::npos)
As noted by #DavidShwartz you can't assume you got the CR and the LF in the same buffer.
{
// New line detected. Process message.
ProcessClientMessage(client);
}
else if (commandBuffer.find(stringBS) != std::string::npos)
{
// Backspace detected,
int size = strlen(client->dataBuffer);
client->dataBuffer[size - 1] = '\0';
This doesn't make any sense. You are using strlen() to tell you where the trailing null is, and then you're putting a null there. You also have the problem that there may not be a trailing null. In any case what you should be doing is removing the backspace and the character before it, which requires different code. You're also operating on the wrong data buffer.
}
else
{
// Strip any extra dumb characters that might have found their way in there
commandBuffer.erase(std::remove(commandBuffer.begin(), commandBuffer.end(), '\r'), commandBuffer.end());
commandBuffer.erase(std::remove(commandBuffer.begin(), commandBuffer.end(), '\n'), commandBuffer.end());
// Add the new data to the clients data buffer
strcat_s(client->dataBuffer, sizeof(client->dataBuffer), commandBuffer.c_str());
}

QByteArray to to 'bitstream' output

I have issues with the following code. I am trying to transmit a QByteArray through a shared bool value (data) between two threads which write and read the data, communicating via 'syncs' and 'ack' bools [Emulating a communications protocol over wires]. Unfortunately when I try to convert the output QByteArray to a bool output, I get an 'ASSERT:' error. Below is the relevant code and output.
void* write(void* wind)
{
// Initialization
int i,b;
Window *w = (Window *)wind;
qDebug() << "Write thread started.";
// Write Loop
while(!w->exit)
{
if(w->enqueue == true)
{
// Reset Trigger
w->enqueue = false;
QByteArray cachedByteArray(w->byteArrayOut);
// Convert from QByteArray to QBitArray
qDebug() << "Starting bit stream" << endl;
for(i=0; i<(cachedByteArray.count()); ++i)
{
qDebug() << "Byte: " << i << endl;
for(b=0; b<8; ++b)
{
qDebug() << "Bit: " << b << endl;
data = cachedByteArray.at(i)&(1<<b);
qDebug() << "Sent Bit: " << b << endl;
syncs = true;
while(!ack); // Wait for bit to be read
syncs = false;
while(ack); // Wait for read thread to be ready
}
}
}
}
Console output:
Write thread started.
Starting bit stream
Byte: 0
Bit: 0
Sent Bit: 0
Bit: 1
ASSERT: "i >= 0 && i < size()" in file /usr/include/qt4/QtCore/qbytearray.h, line 414
The program has unexpectedly finished.
As you can see from the debug messages, the loop works for the first iteration, but crashes on the output of the second bit. I have checked other code sources for conversion between QByteArray and bits and the syntax is correct as far as I can tell.
Thanks for your help.

Qt C++ and QSerialDevice: Windows 7 USB->Serial Port Reading/Writing

I am attempting to read from/write to an RS-232 capable device. This works without issue on Linux. The device is connected via a Digitus USB/Serial Adapter.
The device shows up in Device Manager as COM4.
void PayLife::run() {
this->sendingData = 0;
this->running = true;
qDebug() << "Starting PayLife Thread";
this->port = new AbstractSerial();
this->port->setDeviceName(this->addy);
QByteArray ba;
if (port->open(AbstractSerial::ReadWrite| AbstractSerial::Unbuffered)) {
if (!port->setBaudRate(AbstractSerial::BaudRate19200)) {
qDebug() << "Set baud rate " << AbstractSerial::BaudRate19200 << " error.";
goto end_thread;
};
if (!port->setDataBits(AbstractSerial::DataBits7)) {
qDebug() << "Set data bits " << AbstractSerial::DataBits7 << " error.";
goto end_thread;
}
if (!port->setParity(AbstractSerial::ParityEven)) {
qDebug() << "Set parity " << AbstractSerial::ParityEven << " error.";
goto end_thread;
}
if (!port->setStopBits(AbstractSerial::StopBits1)) {
qDebug() << "Set stop bits " << AbstractSerial::StopBits1 << " error.";
goto end_thread;
}
if (!port->setFlowControl(AbstractSerial::FlowControlOff)) {
qDebug() << "Set flow " << AbstractSerial::FlowControlOff << " error.";
goto end_thread;
}
while(this->running) {
if ((port->bytesAvailable() > 0) || port->waitForReadyRead(900)) {
ba.clear();
ba = port->read(1024);
qDebug() << "Readed is : " << ba.size() << " bytes";
}
else {
qDebug() << "Timeout read data in time : " << QTime::currentTime();
}
}
}
end_thread:
this->running = false;
}
On Linux, I don't use QSerialDevice, just regular serial reading/writing.
No matter what, I always get:
Starting PayLife Thread
Readed is : 0 bytes
Timeout read data in time : QTime("16:27:43")
Timeout read data in time : QTime("16:27:44")
Timeout read data in time : QTime("16:27:45")
Timeout read data in time : QTime("16:27:46")
I am not exactly sure why.
Note, I tried first to use regular Windows API reading and writing with the same results, i.e. it just doesn't ready any data from the device.
I am 100% sure that there is always something to read from the device, as it spams ENQ across the connection.
You should generate the doxygen documentation of QSerialDevice if you haven't already done so. The problem seems to be explained there.
On Windows in unbuffered mode:
Necessary to avoid the values of CharIntervalTimeout and
TotalReadConstantTimeout equal to 0. In theory, it was planned that at
zero values of timeouts method AbstractSerial::read() will read the
data which are in the buffer device driver (not to be confused with
the buffer AbstractSerial!) and return them immediately. But for
unknown reasons, this reading always returns 0, not depending on
whether or not a ready-made data in the buffer.
Because read waits for the data in unbuffered mode, I guess waitForReadyReady doesn't do anything useful in that mode.

Resources