Newer
Older
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009 - 2011 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project
QGROUNDCONTROL is free software: you can redistribute it and/or modify
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.
QGROUNDCONTROL is distributed in the hope that it will be useful,
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
along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
/**
* @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 <iostream>
#include "QGCFlightGearLink.h"
#include "QGC.h"
#include <QHostInfo>
QGCFlightGearLink::QGCFlightGearLink(UASInterface* mav, QString startupArguments, QString remoteHost, QHostAddress host, quint16 port) :
flightGearVersion(0),
startupArguments(startupArguments)
{
this->host = host;
this->connectState = false;
this->name = tr("FlightGear Link (port:%1)").arg(port);
}
QGCFlightGearLink::~QGCFlightGearLink()
{ //do not disconnect unless it is connected.
//disconnectSimulation will delete the memory that was allocated for proces, terraSync and socket
if(connectState){
disconnectSimulation();
}
}
/**
* @brief Runs the thread
*
**/
void QGCFlightGearLink::run()
{
exec();
}
void QGCFlightGearLink::setPort(int port)
{
this->port = port;
}
void QGCFlightGearLink::processError(QProcess::ProcessError err)
{
switch(err)
{
case QProcess::FailedToStart:
MainWindow::instance()->showCriticalMessage(tr("FlightGear/TerraSync Failed to Start"), tr("Please check if the path and command is correct"));
MainWindow::instance()->showCriticalMessage(tr("FlightGear/TerraSync Crashed"), tr("This is a FlightGear-related problem. Please upgrade FlightGear"));
MainWindow::instance()->showCriticalMessage(tr("FlightGear/TerraSync Start Timed Out"), tr("Please check if the path and command is correct"));
MainWindow::instance()->showCriticalMessage(tr("Could not Communicate with FlightGear/TerraSync"), tr("Please check if the path and command is correct"));
MainWindow::instance()->showCriticalMessage(tr("Could not Communicate with FlightGear/TerraSync"), tr("Please check if the path and command is correct"));
MainWindow::instance()->showCriticalMessage(tr("FlightGear/TerraSync Error"), tr("Please check if the path and command is correct."));
/**
* @param host Hostname in standard formatting, e.g. localhost:14551 or 192.168.1.1:14551
*/
void QGCFlightGearLink::setRemoteHost(const QString& host)
{
if (host.contains(":"))
{
//qDebug() << "HOST: " << host.split(":").first();
QHostInfo info = QHostInfo::fromName(host.split(":").first());
if (info.error() == QHostInfo::NoError)
{
// Add host
QList<QHostAddress> hostAddresses = info.addresses();
QHostAddress address;
for (int i = 0; i < hostAddresses.size(); i++)
{
// Exclude loopback IPv4 and all IPv6 addresses
if (!hostAddresses.at(i).toString().contains(":"))
{
address = hostAddresses.at(i);
}
}
currentHost = address;
//qDebug() << "Address:" << address.toString();
// Set port according to user input
}
}
else
{
QHostInfo info = QHostInfo::fromName(host);
if (info.error() == QHostInfo::NoError)
{
// Add host
currentHost = info.addresses().first();
}
}
}
void QGCFlightGearLink::updateActuators(uint64_t time, float act1, float act2, float act3, float act4, float act5, float act6, float act7, float act8)
{
Bryant
committed
Q_UNUSED(time);
Q_UNUSED(act1);
Q_UNUSED(act2);
Q_UNUSED(act3);
Q_UNUSED(act4);
Q_UNUSED(act5);
Q_UNUSED(act6);
Q_UNUSED(act7);
Q_UNUSED(act8);
void QGCFlightGearLink::updateControls(uint64_t time, float rollAilerons, float pitchElevator, float yawRudder, float throttle, uint8_t systemMode, uint8_t navMode)
Q_UNUSED(time);
Q_UNUSED(systemMode);
Q_UNUSED(navMode);
Thomas Gubler
committed
if(!isnan(rollAilerons) && !isnan(pitchElevator) && !isnan(yawRudder) && !isnan(throttle))
{
QString state("%1\t%2\t%3\t%4\t%5\n");
state = state.arg(rollAilerons).arg(pitchElevator).arg(yawRudder).arg(true).arg(throttle);
writeBytes(state.toAscii().constData(), state.length());
}
else
{
qDebug() << "HIL: Got NaN values from the hardware: isnan output: roll: " << isnan(rollAilerons) << ", pitch: " << isnan(pitchElevator) << ", yaw: " << isnan(yawRudder) << ", throttle: " << isnan(throttle);
}
void QGCFlightGearLink::writeBytes(const char* data, qint64 size)
{
#ifdef QGCFlightGearLink_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)
{
}
else
{
ascii.append(219);
}
}
qDebug() << "Sent" << size << "bytes to" << currentHost.toString() << ":" << currentPort << "data:";
qDebug() << bytes;
qDebug() << "ASCII:" << ascii;
#endif
if (connectState && socket) socket->writeDatagram(data, size, currentHost, currentPort);
}
/**
* @brief Read a number of bytes from the interface.
*
* @param data Pointer to the data byte array to write the bytes to
* @param maxLength The maximum number of bytes to write
**/
void QGCFlightGearLink::readBytes()
{
const qint64 maxLength = 65536;
char data[maxLength];
QHostAddress sender;
quint16 senderPort;
unsigned int s = socket->pendingDatagramSize();
if (s > maxLength) std::cerr << __FILE__ << __LINE__ << " UDP datagram overflow, allowed to read less bytes than datagram size" << std::endl;
socket->readDatagram(data, maxLength, &sender, &senderPort);
QByteArray b(data, s);
QStringList values = state.split("\t");
// Check length
if (values.size() != 17)
{
qDebug() << "RETURN LENGTH MISMATCHING EXPECTED" << 17 << "BUT GOT" << values.size();
qDebug() << state;
return;
}
// Parse string
float roll, pitch, yaw, rollspeed, pitchspeed, yawspeed;
// XXX add
float ind_airspeed = 0.0f;
float true_airspeed = 0.0f;
lat = values.at(1).toDouble();
lon = values.at(2).toDouble();
alt = values.at(3).toDouble();
roll = values.at(4).toDouble();
pitch = values.at(5).toDouble();
yaw = values.at(6).toDouble();
rollspeed = values.at(7).toDouble();
pitchspeed = values.at(8).toDouble();
yawspeed = values.at(9).toDouble();
xacc = values.at(10).toDouble();
yacc = values.at(11).toDouble();
zacc = values.at(12).toDouble();
vx = values.at(13).toDouble();
vy = values.at(14).toDouble();
vz = values.at(15).toDouble();
// Send updated state
emit hilStateChanged(QGC::groundTimeUsecs(), roll, pitch, yaw, rollspeed,
pitchspeed, yawspeed, lat, lon, alt,
vx, vy, vz, ind_airspeed, true_airspeed, xacc, yacc, zacc);
// // 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;
}
/**
* @brief Get the number of bytes to read.
*
* @return The number of bytes to read
**/
qint64 QGCFlightGearLink::bytesAvailable()
{
return socket->pendingDatagramSize();
}
/**
* @brief Disconnect the connection.
*
* @return True if connection has been disconnected, false if connection couldn't be disconnected.
**/
{
this, SLOT(processError(QProcess::ProcessError)));
disconnect(mav, SIGNAL(hilControlsChanged(uint64_t, float, float, float, float, uint8_t, uint8_t)), this, SLOT(updateControls(uint64_t,float,float,float,float,uint8_t,uint8_t)));
disconnect(this, SIGNAL(hilStateChanged(quint64,float,float,float,float,float,float,double,double,double,float,float,float,float,float,float,float,float)), mav, SLOT(sendHilState(quint64,float,float,float,float,float,float,double,double,double,float,float,float,float,float,float,float,float)));
if (process)
{
process->close();
delete process;
process = NULL;
}
if (terraSync)
{
terraSync->close();
delete terraSync;
terraSync = NULL;
}
connectState = false;
Lorenz Meier
committed
emit simulationDisconnected();
emit simulationConnected(false);
return !connectState;
}
/**
* @brief Connect the connection.
*
* @return True if connection has been established, false if connection couldn't be established.
**/
{
qDebug() << "STARTING FLIGHTGEAR LINK";
socket = new QUdpSocket(this);
connectState = socket->bind(host, port);
QObject::connect(socket, SIGNAL(readyRead()), this, SLOT(readBytes()));
connect(mav, SIGNAL(hilControlsChanged(uint64_t, float, float, float, float, uint8_t, uint8_t)), this, SLOT(updateControls(uint64_t,float,float,float,float,uint8_t,uint8_t)));
connect(this, SIGNAL(hilStateChanged(quint64,float,float,float,float,float,float,double,double,double,float,float,float,float,float,float,float,float)), mav, SLOT(sendHilState(quint64,float,float,float,float,float,float,double,double,double,float,float,float,float,float,float,float,float)));
UAS* uas = dynamic_cast<UAS*>(mav);
if (uas)
{
uas->startHil();
}
//connect(&refreshTimer, SIGNAL(timeout()), this, SLOT(sendUAVUpdate()));
// Catch process error
QObject::connect( process, SIGNAL(error(QProcess::ProcessError)),
this, SLOT(processError(QProcess::ProcessError)));
QObject::connect( terraSync, SIGNAL(error(QProcess::ProcessError)),
this, SLOT(processError(QProcess::ProcessError)));
processFgfs = "/Applications/FlightGear.app/Contents/Resources/fgfs";
processTerraSync = "/Applications/FlightGear.app/Contents/Resources/terrasync";
Thomas Gubler
committed
//fgRoot = "/Applications/FlightGear.app/Contents/Resources/data";
//fgScenery = "/Applications/FlightGear.app/Contents/Resources/data/Scenery";
Thomas Gubler
committed
terraSyncScenery = "/Applications/FlightGear.app/Contents/Resources/data/Scenery-TerraSync";
processFgfs = "C:\\Program Files (x86)\\FlightGear\\bin\\Win32\\fgfs";
Thomas Gubler
committed
//fgRoot = "C:\\Program Files (x86)\\FlightGear\\data";
terraSyncScenery = "C:\\Program Files (x86)\\FlightGear\\data\\Scenery-Terrasync";
Thomas Gubler
committed
processFgfs = "fgfs";
//fgRoot = "/usr/share/games/flightgear";
//fgScenery = "/usr/share/games/flightgear/Scenery/";
processTerraSync = "nice"; //according to http://wiki.flightgear.org/TerraSync, run with lower priority
terraSyncScenery = QDir::homePath() + "/.terrasync/Scenery"; //according to http://wiki.flightgear.org/TerraSync a separate directory is used
fgAircraft = QApplication::applicationDirPath() + "/files/flightgear/Aircraft";
Thomas Gubler
committed
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
// QFileInfo executable(processFgfs);
// if (!executable.isExecutable())
// {
// MainWindow::instance()->showCriticalMessage(tr("FlightGear Failed to Start"), tr("FlightGear was not found at %1").arg(processFgfs));
// sane = false;
// }
// QFileInfo root(fgRoot);
// if (!root.isDir())
// {
// MainWindow::instance()->showCriticalMessage(tr("FlightGear Failed to Start"), tr("FlightGear data directory was not found at %1").arg(fgRoot));
// sane = false;
// }
// QFileInfo scenery(fgScenery);
// if (!scenery.isDir())
// {
// MainWindow::instance()->showCriticalMessage(tr("FlightGear Failed to Start"), tr("FlightGear scenery directory was not found at %1").arg(fgScenery));
// sane = false;
// }
// QFileInfo terraSyncExecutableInfo(processTerraSync);
// if (!terraSyncExecutableInfo.isExecutable())
// {
// MainWindow::instance()->showCriticalMessage(tr("FlightGear Failed to Start"), tr("TerraSync was not found at %1").arg(processTerraSync));
// sane = false;
// }
if (!sane) return false;
// --atlas=socket,out,1,localhost,5505,udp
// terrasync -p 5505 -S -d /usr/local/share/TerraSync
//flightGearArguments << QString("--fg-root=%1").arg(fgRoot);
Thomas Gubler
committed
flightGearArguments << QString("--fg-scenery=%1:%2").arg(terraSyncScenery); //according to http://wiki.flightgear.org/TerraSync a separate directory is used
flightGearArguments << QString("--fg-aircraft=%1").arg(fgAircraft);
Thomas Gubler
committed
flightGearArguments << QString("--generic=socket,out,50,127.0.0.1,%1,udp,qgroundcontrol-quadrotor").arg(port);
flightGearArguments << QString("--generic=socket,in,50,127.0.0.1,%1,udp,qgroundcontrol-quadrotor").arg(currentPort);
Thomas Gubler
committed
flightGearArguments << QString("--generic=socket,out,50,127.0.0.1,%1,udp,qgroundcontrol-fixed-wing").arg(port);
flightGearArguments << QString("--generic=socket,in,50,127.0.0.1,%1,udp,qgroundcontrol-fixed-wing").arg(currentPort);
flightGearArguments << "--atlas=socket,out,1,localhost,5505,udp";
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
// flightGearArguments << "--in-air";
// flightGearArguments << "--roll=0";
// flightGearArguments << "--pitch=0";
// flightGearArguments << "--vc=90";
// flightGearArguments << "--heading=300";
// flightGearArguments << "--timeofday=noon";
// flightGearArguments << "--disable-hud-3d";
// flightGearArguments << "--disable-fullscreen";
// flightGearArguments << "--geometry=400x300";
// flightGearArguments << "--disable-anti-alias-hud";
// flightGearArguments << "--wind=0@0";
// flightGearArguments << "--turbulence=0.0";
// flightGearArguments << "--prop:/sim/frame-rate-throttle-hz=30";
// flightGearArguments << "--control=mouse";
// flightGearArguments << "--disable-intro-music";
// flightGearArguments << "--disable-sound";
// flightGearArguments << "--disable-random-objects";
// flightGearArguments << "--disable-ai-models";
// flightGearArguments << "--shading-flat";
// flightGearArguments << "--fog-disable";
// flightGearArguments << "--disable-specular-highlight";
// //flightGearArguments << "--disable-skyblend";
// flightGearArguments << "--disable-random-objects";
// flightGearArguments << "--disable-panel";
// //flightGearArguments << "--disable-horizon-effect";
// flightGearArguments << "--disable-clouds";
// flightGearArguments << "--fdm=jsb";
// flightGearArguments << "--units-meters"; //XXX: check: the protocol xml has already a conversion from feet to m?
// flightGearArguments << "--notrim";
flightGearArguments += startupArguments.split(" ");
if (mav->getSystemType() == MAV_TYPE_QUADROTOR)
{
flightGearArguments << "--prop:/engines/engine[0]/running=true";
flightGearArguments << "--prop:/engines/engine[1]/running=true";
flightGearArguments << "--prop:/engines/engine[2]/running=true";
flightGearArguments << "--prop:/engines/engine[3]/running=true";
flightGearArguments << "--prop:/engines/engine/running=true";
flightGearArguments << QString("--lat=%1").arg(UASManager::instance()->getHomeLatitude());
flightGearArguments << QString("--lon=%1").arg(UASManager::instance()->getHomeLongitude());
flightGearArguments << QString("--altitude=%1").arg(UASManager::instance()->getHomeAltitude());
// Add new argument with this: flightGearArguments << "";
//flightGearArguments << QString("--aircraft=%2").arg(aircraft);
Thomas Gubler
committed
terraSyncArguments << "terrasync";
#endif
terraSyncArguments << "-p";
terraSyncArguments << "5505";
terraSyncArguments << "-d";
terraSyncArguments << terraSyncScenery; //according to http://wiki.flightgear.org/TerraSync a separate directory is used
Thomas Gubler
committed
#ifdef Q_OS_LINUX
/* Setting environment */
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
process->setProcessEnvironment(env);
terraSync->setProcessEnvironment(env);
#endif
// connect (terraSync, SIGNAL(readyReadStandardOutput()), this, SLOT(printTerraSyncOutput()));
// connect (terraSync, SIGNAL(readyReadStandardError()), this, SLOT(printTerraSyncError()));
// qDebug() << "STARTING: " << processTerraSync << terraSyncArguments;
process->start(processFgfs, flightGearArguments);
Lorenz Meier
committed
emit simulationConnected(connectState);
Lorenz Meier
committed
emit simulationConnected();
connectionStartTime = QGC::groundTimeUsecs()/1000;
}
qDebug() << "STARTING SIM";
// qDebug() << "STARTING: " << processFgfs << flightGearArguments;
return connectState;
}
void QGCFlightGearLink::printTerraSyncOutput()
{
qDebug() << "TerraSync stdout:";
QByteArray byteArray = terraSync->readAllStandardOutput();
QStringList strLines = QString(byteArray).split("\n");
foreach (QString line, strLines){
qDebug() << line;
}
}
void QGCFlightGearLink::printTerraSyncError()
{
qDebug() << "TerraSync stderr:";
QByteArray byteArray = terraSync->readAllStandardError();
QStringList strLines = QString(byteArray).split("\n");
foreach (QString line, strLines){
qDebug() << line;
}
}
/**
* @brief Set the startup arguments used to start flightgear
*
**/
void QGCFlightGearLink::setStartupArguments(QString startupArguments)
{
this->startupArguments = startupArguments;
}
/**
* @brief Check if connection is active.
*
* @return True if link is connected, false otherwise.
**/
bool QGCFlightGearLink::isConnected()
{
return connectState;
}
QString QGCFlightGearLink::getName()
{
return name;
}
QString QGCFlightGearLink::getRemoteHost()
{
return QString("%1:%2").arg(currentHost.toString(), currentPort);
}
void QGCFlightGearLink::setName(QString name)
{
this->name = name;