Skip to content
UDPLink.cc 11.8 KiB
Newer Older
pixhawk's avatar
pixhawk committed
/*=====================================================================

lm's avatar
lm committed
QGroundControl Open Source Ground Control Station
pixhawk's avatar
pixhawk committed

(c) 2009 - 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
pixhawk's avatar
pixhawk committed

lm's avatar
lm committed
This file is part of the QGROUNDCONTROL project
pixhawk's avatar
pixhawk committed

lm's avatar
lm committed
    QGROUNDCONTROL is free software: you can redistribute it and/or modify
pixhawk's avatar
pixhawk committed
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

lm's avatar
lm committed
    QGROUNDCONTROL is distributed in the hope that it will be useful,
pixhawk's avatar
pixhawk committed
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
lm's avatar
lm committed
    along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
pixhawk's avatar
pixhawk committed

======================================================================*/

/**
 * @file
 *   @brief Definition of UDP connection (server) for unmanned vehicles
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

#include <QTimer>
#include <QList>
#include <QDebug>
#include <QMutexLocker>
#include <QNetworkProxy>
pixhawk's avatar
pixhawk committed
#include <iostream>
pixhawk's avatar
pixhawk committed
#include "UDPLink.h"
#include <QHostInfo>
pixhawk's avatar
pixhawk committed

static bool is_ip(const QString& address)
{
    int a,b,c,d;
    if (sscanf(address.toStdString().c_str(), "%d.%d.%d.%d", &a, &b, &c, &d) != 4)
        return false;
    return true;
}

static QString get_ip_address(const QString& address)
{
    if(is_ip(address))
        return address;
    // Need to look it up
    QHostInfo info = QHostInfo::fromName(address);
    if (info.error() == QHostInfo::NoError)
    {
        QList<QHostAddress> hostAddresses = info.addresses();
        QHostAddress address;
        for (int i = 0; i < hostAddresses.size(); i++)
        {
            // Exclude all IPv6 addresses
            if (!hostAddresses.at(i).toString().contains(":"))
            {
                return hostAddresses.at(i).toString();
            }
        }
    }
    return QString("");
}

UDPLink::UDPLink(UDPConfiguration* config)
    : _socket(NULL)
    , _connectState(false)
pixhawk's avatar
pixhawk committed
{
    Q_ASSERT(config != NULL);
    _config = config;
    _config->setLink(this);

    // We're doing it wrong - because the Qt folks got the API wrong:
    // http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/
    moveToThread(this);
    qDebug() << "UDP Created " << _config->name();
pixhawk's avatar
pixhawk committed
}

UDPLink::~UDPLink()
{
    // Disconnect link from configuration
    _config->setLink(NULL);
Lorenz Meier's avatar
Lorenz Meier committed
    // Tell the thread to exit
    quit();
    // Wait for it to exit
    wait();
pixhawk's avatar
pixhawk committed
}

/**
 * @brief Runs the thread
 *
 **/
void UDPLink::run()
{
pixhawk's avatar
pixhawk committed
}

pixhawk's avatar
pixhawk committed
{
pixhawk's avatar
pixhawk committed
}

pixhawk's avatar
pixhawk committed
{
}

void UDPLink::addHost(const QString& host)
{
void UDPLink::removeHost(const QString& host)
pixhawk's avatar
pixhawk committed
}

pixhawk's avatar
pixhawk committed
void UDPLink::writeBytes(const char* data, qint64 size)
{
    // Broadcast to all connected systems
    QString host;
    int port;
    if(_config->firstHost(host, port)) {
        do {
            if(UDPLINK_DEBUG) {
                QString bytes;
                QString ascii;
                for (int i=0; i<size; i++)
                {
                    unsigned char v = data[i];
                    bytes.append(QString().sprintf("%02x ", v));
                    if (data[i] > 31 && data[i] < 127)
                    {
                        ascii.append(data[i]);
                    }
                    else
                    {
                        ascii.append(219);
                    }
                }
                qDebug() << "Sent" << size << "bytes to" << host << ":" << port << "data:";
                qDebug() << bytes;
                qDebug() << "ASCII:" << ascii;
lm's avatar
lm committed
            }
            QHostAddress currentHost(host);
            _socket->writeDatagram(data, size, currentHost, (quint16)port);
            // Log the amount and time written out for future data rate calculations.
            QMutexLocker dataRateLocker(&dataRateMutex);
            logDataRateToBuffer(outDataWriteAmounts, outDataWriteTimes, &outDataIndex, size, QDateTime::currentMSecsSinceEpoch());
        } while (_config->nextHost(host, port));
pixhawk's avatar
pixhawk committed
}

/**
 * @brief Read a number of bytes from the interface.
 **/
pixhawk's avatar
pixhawk committed
{
    {
        QByteArray datagram;
        datagram.resize(_socket->pendingDatagramSize());
pixhawk's avatar
pixhawk committed

        QHostAddress sender;
        quint16 senderPort;
        _socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
pixhawk's avatar
pixhawk committed

        // FIXME TODO Check if this method is better than retrieving the data by individual processes
        emit bytesReceived(this, datagram);
pixhawk's avatar
pixhawk committed

        // Log this data reception for this timestep
        QMutexLocker dataRateLocker(&dataRateMutex);
        logDataRateToBuffer(inDataWriteAmounts, inDataWriteTimes, &inDataIndex, datagram.length(), QDateTime::currentMSecsSinceEpoch());

//        // Echo data for debugging purposes
//        std::cerr << __FILE__ << __LINE__ << "Received datagram:" << std::endl;
//        int i;
//        for (i=0; i<s; i++)
//        {
//            unsigned int v=data[i];
//            fprintf(stderr,"%02x ", v);
//        }
//        std::cerr << std::endl;
pixhawk's avatar
pixhawk committed

        // TODO This doesn't validade the sender. Anything sending UDP packets to this port gets
        // added to the list and will start receiving datagrams from here. Even a port scanner
        // would trigger this.
        // Add host to broadcast list if not yet present, or update its port
dogmaphobic's avatar
dogmaphobic committed
        _config->addHost(sender.toString(), (int)senderPort);
pixhawk's avatar
pixhawk committed
    }
}

/**
 * @brief Disconnect the connection.
 *
 * @return True if connection has been disconnected, false if connection couldn't be disconnected.
 **/
bool UDPLink::_disconnect(void)
pixhawk's avatar
pixhawk committed
{
    this->quit();
    this->wait();
    if (_socket) {
        // Make sure delete happen on correct thread
        _socket->close();
        emit disconnected();
    }
    // TODO When would this ever return false?
    _connectState = false;
    return !_connectState;
pixhawk's avatar
pixhawk committed
}

/**
 * @brief Connect the connection.
 *
 * @return True if connection has been established, false if connection couldn't be established.
 **/
bool UDPLink::_connect(void)
pixhawk's avatar
pixhawk committed
{
    if(this->isRunning())
    {
        this->quit();
        this->wait();
    }
    // TODO When would this ever return false?
    bool connected = true;
oberion's avatar
oberion committed
    start(HighPriority);
oberion's avatar
oberion committed
}

oberion's avatar
oberion committed
{
    QHostAddress host = QHostAddress::Any;
    _socket = new QUdpSocket();
    _socket->setProxy(QNetworkProxy::NoProxy);
    _connectState = _socket->bind(host, _config->localPort(), QAbstractSocket::ReuseAddressHint);
        QObject::connect(_socket, SIGNAL(readyRead()), this, SLOT(readBytes()));
    } else {
        emit communicationError("UDP Link Error", "Error binding UDP port");
pixhawk's avatar
pixhawk committed
}

/**
 * @brief Check if connection is active.
 *
 * @return True if link is connected, false otherwise.
 **/
bool UDPLink::isConnected() const
pixhawk's avatar
pixhawk committed
}

qint64 UDPLink::getConnectionSpeed() const
pixhawk's avatar
pixhawk committed
{
    return 54000000; // 54 Mbit
}

qint64 UDPLink::getCurrentInDataRate() const
{
    return 0;
pixhawk's avatar
pixhawk committed
}

qint64 UDPLink::getCurrentOutDataRate() const
pixhawk's avatar
pixhawk committed
{
pixhawk's avatar
pixhawk committed
}

//--------------------------------------------------------------------------
//-- UDPConfiguration
UDPConfiguration::UDPConfiguration(const QString& name) : LinkConfiguration(name)
UDPConfiguration::UDPConfiguration(UDPConfiguration* source) : LinkConfiguration(source)
    _localPort = source->localPort();
    _hosts.clear();
    QString host;
    int port;
    if(source->firstHost(host, port)) {
        do {
            addHost(host, port);
        } while(source->nextHost(host, port));
    }
void UDPConfiguration::copyFrom(LinkConfiguration *source)
    LinkConfiguration::copyFrom(source);
    UDPConfiguration* usource = dynamic_cast<UDPConfiguration*>(source);
    Q_ASSERT(usource != NULL);
    _localPort = usource->localPort();
    QString host;
    int port;
    if(usource->firstHost(host, port)) {
        do {
            addHost(host, port);
        } while(usource->nextHost(host, port));
    }
}

/**
 * @param host Hostname in standard formatt, e.g. localhost:14551 or 192.168.1.1:14551
 */
void UDPConfiguration::addHost(const QString& host)
{
    // Handle x.x.x.x:p
        addHost(host.split(":").first(), host.split(":").last().toInt());
    // If no port, use default
dogmaphobic's avatar
dogmaphobic committed
        addHost(host, (int)_localPort);
    }
}

void UDPConfiguration::addHost(const QString& host, int port)
{
dogmaphobic's avatar
dogmaphobic committed
    QMutexLocker locker(&_confMutex);
    if(_hosts.contains(host)) {
        if(_hosts[host] != port) {
            _hosts[host] = port;
        }
    } else {
        QString ipAdd = get_ip_address(host);
        if(ipAdd.isEmpty()) {
            qWarning() << "UDP:" << "Could not resolve" << host;
        } else {
            _hosts[ipAdd] = port;
            qDebug() << "UDP:" << "Adding Host:" << ipAdd << ":" << port;
dogmaphobic's avatar
dogmaphobic committed
        }
    }
}

void UDPConfiguration::removeHost(const QString& host)
{
dogmaphobic's avatar
dogmaphobic committed
    QMutexLocker locker(&_confMutex);
    QString tHost = host;
    if (tHost.contains(":")) {
        tHost = tHost.split(":").first();
    }
    tHost = tHost.trimmed();
    QMap<QString, int>::iterator i = _hosts.find(tHost);
    if(i != _hosts.end()) {
        _hosts.erase(i);
    }
}

bool UDPConfiguration::firstHost(QString& host, int& port)
{
dogmaphobic's avatar
dogmaphobic committed
    _confMutex.lock();
    _it = _hosts.begin();
    if(_it == _hosts.end()) {
dogmaphobic's avatar
dogmaphobic committed
        _confMutex.unlock();
dogmaphobic's avatar
dogmaphobic committed
    _confMutex.unlock();
    return nextHost(host, port);
}

bool UDPConfiguration::nextHost(QString& host, int& port)
{
dogmaphobic's avatar
dogmaphobic committed
    QMutexLocker locker(&_confMutex);
    if(_it != _hosts.end()) {
        host = _it.key();
        port = _it.value();
        _it++;
        return true;
    }
    return false;
}

void UDPConfiguration::setLocalPort(quint16 port)
{
    _localPort = port;
}

void UDPConfiguration::saveSettings(QSettings& settings, const QString& root)
{
    _confMutex.lock();
    settings.beginGroup(root);
    settings.setValue("port", (int)_localPort);
    settings.setValue("hostCount", _hosts.count());
    int index = 0;
    QMap<QString, int>::const_iterator it = _hosts.begin();
    while(it != _hosts.end()) {
        QString hkey = QString("host%1").arg(index);
        settings.setValue(hkey, it.key());
        QString pkey = QString("port%1").arg(index);
        settings.setValue(pkey, it.value());
        it++;
        index++;
    }
    settings.endGroup();
    _confMutex.unlock();
}

void UDPConfiguration::loadSettings(QSettings& settings, const QString& root)
{
    _confMutex.lock();
    _hosts.clear();
dogmaphobic's avatar
dogmaphobic committed
    _confMutex.unlock();
    settings.beginGroup(root);
    _localPort = (quint16)settings.value("port", QGC_UDP_LOCAL_PORT).toUInt();
    int hostCount = settings.value("hostCount", 0).toInt();
    for(int i = 0; i < hostCount; i++) {
        QString hkey = QString("host%1").arg(i);
        QString pkey = QString("port%1").arg(i);
        if(settings.contains(hkey) && settings.contains(pkey)) {
            addHost(settings.value(hkey).toString(), settings.value(pkey).toInt());
        }
    }
    settings.endGroup();
}

void UDPConfiguration::updateSettings()
{
    if(_link) {
        UDPLink* ulink = dynamic_cast<UDPLink*>(_link);
        if(ulink) {
            ulink->_restartConnection();
        }
    }