0

I'm trying to control a robot wirelessly through an Arduino (using a X360 controller on a computer), which requires very low latency. I chose Wifi for this reason (and the fact I'll be streaming video), and after a little test it turns out I have a huge lag using TCP. Is this normal (with 54Mbits/s, it shouldn't!)? How can I reduce it to be controllable?

Server code (Arduino sketch):

#include <SPI.h>
#include <Ethernet.h>

byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x48, 0x0D }; 
byte ip[] = { 192, 168, 0, 11 };    
byte gateway[] = { 192, 168, 0, 254 };
byte subnet[] = { 255, 255, 255, 0 };
byte localPort = 99;

EthernetServer server = EthernetServer(localPort);

void setup()
{
  // initialize the ethernet device
  Ethernet.begin(mac, ip, gateway, subnet);
  Serial.begin(9600);

  // start listening for clients
  server.begin();
  Serial.println("Server ready");
}

void loop()
{
  // if an incoming client connects, there will be bytes available to read:
  EthernetClient client = server.available();
  if (client == true) {
    Serial.println("Received:");

    byte received = 0;
    while((received = client.read()) != -1) {
      Serial.println(received);
      server.write(received);
    }

    Serial.println("Over\n"); 
  }
}

Client code (PC, QtCreator):

#include <QTextStream>
#include <QTCPSocket>

QString arduinoIP = "192.168.0.11";
char arduinoPort = 99;

int main(void)
{
    QTcpSocket socket;
    QTextStream in(stdin);
    QTextStream out(stdout);

    out << "Connection... "; out.flush();
    socket.connectToHost(arduinoIP, arduinoPort);
    if(!socket.waitForConnected(5000)) {
        out << socket.errorString() << "\n";
    }
    else {
        out << "OK\n"; out.flush(); //I don't know why \n doesn't flush

        out << "Type a message to send to the Arduino or quit to exit\n"; out.flush();

        QString command;
        in >> command;

        while(command != "quit") {
            QByteArray bufOut = command.toUtf8();
            socket.write(bufOut);
            socket.waitForReadyRead(1000); //Wait for answer (temp)
            out << "Answer: " << socket.readAll() << "\n";
        }
    }

    return 0;
}

Thank you in advance for your help.

Regards, Mister Mystère

Mister Mystère
  • 952
  • 2
  • 16
  • 39
  • I thought it would also be a good idea to post that because there is absolutely NOWHERE (to my knowledge after hours of searching) on the web where we can find a simple application (server AND client) for communicating between a PC and an arduino via ethernet/wifi without using HTTP. Normally this comment should be enough to be indexed by Google and help those who are looking for this as well. With that in mind, please feel free to help me improve this code as well. – Mister Mystère Jun 09 '13 at 19:43

3 Answers3

4

A TCP connection requires more packets to provide the reliable transport of the data. TCP is not meant for low latency, it is meant for reliably transmitting a stream of data. For example, if you are sending a file, you need all the packets to be received and to be pieced together in the correct order.

You are seeing the fact that bandwidth and latency are unrelated. Most streaming video systems pre-buffer data to provide the illusion of no interruptions in the transport stream. The underlying behavior is that transport latency can be jittery, but the buffered data keeps the perception of a continuous stream.

For your application, consider using UDP.

UDP on Arduino

TCP is a stream, UDP is for small messages. Your decision will pivot on the question:

What is the effect if a packet is sent but never received?

In the case of a controller input, it may be better to simply ignore the lost data and receive the next transmission. I presuming from your question that you will be repeatedly sending the state of the controller (up down left right?) If so, UDP is you choice. If you choose TCP, the behavior will be to keep retrying to recover this lost data before sending the next data. In your use case, you might as well send the next controller state rather than trying to recover the stream.

jdr5ca
  • 2,809
  • 14
  • 25
  • Very well presented answer, thanks a lot. I had considered UDP because I had heard it was faster, but I had preferred TCP instead because I was disturbed with losing data (it forces me to send all the states instead of just a command for the buttons) and I thought the throughput would be enough for the safety protocols. I'll switch to UDP and let you know. - Should I mark the topic as solved and re-do another for UDP in case it goes wrong or leave this one open? – Mister Mystère Jun 10 '13 at 10:39
  • Works like a charm. Thanks, that's a huge difference! – Mister Mystère Jun 10 '13 at 14:55
2

Did you check those quite general remarks?

1) Did you check your Wi-Fi spectrum. Overlapping channels causes drops of packets. These packets will be retransmitted with a small extra delay. A nice tool to help: http://www.metageek.net/products/inssider/

2) Is your Arduino not flooded with broadcast packets of other devices on your network. Maybe your network stack is heavily busy with checking and ignoring broadcast packets. Packets of your TCP-connection can be dropped and also retransmitted. Try a private AP for your PC and Arduino and look to the performance.

Peter DW
  • 486
  • 3
  • 13
  • Thank you for your answer. The computer and the arduino+ethernet shield are all alone connected to a TP-LINK nano which is in AP mode. I'll check the spectrum tomorrow and may have to change the channel accordingly but can it be responsible for such a lag, really? – Mister Mystère Jun 09 '13 at 22:33
  • 1
    I am working on a Miracast solution. This standard sends video over Wi-Fi. In a really bad Wi-Fi enviroment, the latency can grow up towards 400ms longer than normal (not seconds!!). We are also using UDP to send the video data. See the next command for that. Can you also share which processor is on your Arduino? I worked a couple of years back with lwip on a basic microcontroller. If to much traffic was received by the network stack of lwip, the microcontroller came horably slow. That was the reason why I asked you to use a private network. – Peter DW Jun 10 '13 at 08:15
  • It's an Arduino mega 2560. Like jdr5ca said, the latency may come from pre-buffering, I will consider UDP communication instead and get back to you. – Mister Mystère Jun 10 '13 at 10:35
0

UDP is really faster, I used this test code instead:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTimer>
#include <QVBoxLayout>
#include <QSpinBox>
#include <QtNetwork/QUdpSocket>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:
    void onTimer();

private:
    QTimer timer;
    QVBoxLayout layout;
    QWidget centralW;
    QSpinBox sendBox;
    QSpinBox receiveBox;

    QUdpSocket *socket;
};

#endif // MAINWINDOW_H

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    connect(&timer, SIGNAL(timeout()), this, SLOT(onTimer()));
    timer.start(100);
    sendBox.setMaximum(1000);
    layout.addWidget(&sendBox);
    receiveBox.setMaximum(1000);
    layout.addWidget(&receiveBox);
    centralW.setLayout(&layout);
    setCentralWidget(&centralW);

    socket = new QUdpSocket(this);
}

MainWindow::~MainWindow()
{
}

void MainWindow::onTimer() {
    QByteArray datagram = QByteArray::number(sendBox.value());
    socket->writeDatagram(datagram.data(), datagram.size(), QHostAddress("192.168.0.11"), 99);

    if(socket->hasPendingDatagrams()) {
        datagram.resize(socket->pendingDatagramSize());
        socket->readDatagram(datagram.data(), datagram.size());
        receiveBox.setValue(QString(datagram.data()).toInt());
    }
}

And on the server side:

#include <SPI.h>         // needed for Arduino versions later than 0018
#include <Ethernet.h>
#include <EthernetUdp.h>         // UDP library from: bjoern@cs.stanford.edu 12/30/2008

byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x48, 0x0D }; 
byte ip[] = { 192, 168, 0, 11 };    
byte gateway[] = { 192, 168, 0, 254 };
byte subnet[] = { 255, 255, 255, 0 };
byte localPort = 99;

EthernetUDP Udp;

void setup() {
  Ethernet.begin(mac,ip);
  Udp.begin(localPort);

  Serial.begin(9600);
}

void loop() {
  // if there's data available, read a packet
  int packetSize = Udp.parsePacket();
  if(packetSize)
  {
    static char buffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet,

    //Udp.read does not erase the rest of the buffer, without that we would
    //get 989 instead of 98 after having entered 999 for example
    for(int i = 0; i < UDP_TX_PACKET_MAX_SIZE; ++i) buffer[i] = 0; 

    Udp.read(buffer,UDP_TX_PACKET_MAX_SIZE);
    Serial.println(buffer);

    // send a reply, to the IP address and port that sent us the packet we received
    Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
    Udp.write(buffer);
    Udp.endPacket();
  }
}
Mister Mystère
  • 952
  • 2
  • 16
  • 39