2 ******************************************************************************
4 * @file flightgearbridge.h
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
6 * @addtogroup GCSPlugins GCS Plugins
8 * @addtogroup HITLPlugin HITL Plugin
10 * @brief The Hardware In The Loop plugin
11 *****************************************************************************/
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 3 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23 * You should have received a copy of the GNU General Public License along
24 * with this program; if not, write to the Free Software Foundation, Inc.,
25 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 #include "fgsimulator.h"
29 #include "extensionsystem/pluginmanager.h"
30 #include "coreplugin/icore.h"
31 #include "coreplugin/threadmanager.h"
34 #define M_PI 3.14159265358979323846
37 // FGSimulator::FGSimulator(QString hostAddr, int outPort, int inPort, bool manual, QString binPath, QString dataPath) :
38 // Simulator(hostAddr, outPort, inPort, manual, binPath, dataPath),
41 //// Note: Only tested on windows 7
42 // #if defined(Q_WS_WIN)
43 // cmdShell = QString("c:/windows/system32/cmd.exe");
45 // cmdShell = QString("bash");
49 FGSimulator::FGSimulator(const SimulatorSettings
& params
) :
53 udpCounterGCSsend
= 0;
56 FGSimulator::~FGSimulator()
58 disconnect(simProcess
, SIGNAL(readyReadStandardOutput()), this, SLOT(processReadyRead()));
61 void FGSimulator::setupUdpPorts(const QString
& host
, int inPort
, int outPort
)
65 if (inSocket
->bind(QHostAddress(host
), inPort
)) {
66 emit
processOutput("Successfully bound to address " + host
+ " on port " + QString::number(inPort
) + "\n");
68 emit
processOutput("Cannot bind to address " + host
+ " on port " + QString::number(inPort
) + "\n");
72 bool FGSimulator::setupProcess()
74 QMutexLocker
locker(&lock
);
76 // Copy FlightGear generic protocol configuration file to the FG protocol directory
77 // NOTE: Not working on Windows 7, if FG is installed in the "Program Files",
78 // likelly due to permissions. The file should be manually copied to data/Protocol/opfgprotocol.xml
79 // QFile xmlFile(":/flightgear/genericprotocol/opfgprotocol.xml");
80 // xmlFile.open(QIODevice::ReadOnly | QIODevice::Text);
81 // QString xml = xmlFile.readAll();
83 // QFile xmlFileOut(pathData + "/Protocol/opfgprotocol.xml");
84 // xmlFileOut.open(QIODevice::WriteOnly | QIODevice::Text);
85 // xmlFileOut.write(xml.toLatin1());
86 // xmlFileOut.close();
88 Qt::HANDLE mainThread
= QThread::currentThreadId();
90 qDebug() << "setupProcess Thread: " << mainThread
;
92 simProcess
= new QProcess();
93 simProcess
->setReadChannelMode(QProcess::MergedChannels
);
94 connect(simProcess
, SIGNAL(readyReadStandardOutput()), this, SLOT(processReadyRead()));
95 // Note: Only tested on windows 7
97 QString
cmdShell("c:/windows/system32/cmd.exe");
99 QString
cmdShell("bash");
102 // Start shell (Note: Could not start FG directly on Windows, only through terminal!)
103 simProcess
->start(cmdShell
);
104 if (simProcess
->waitForStarted() == false) {
105 emit
processOutput("Error:" + simProcess
->errorString());
110 // Note: The input generic protocol is set to update at a much higher rate than the actual updates are sent by the GCS.
111 // If this is not done then a lag will be introduced by FlightGear, likelly because the receive socket buffer builds up during startup.
112 QString
args("--fg-root=\"" + settings
.dataPath
+ "\" " +
113 "--timeofday=noon " +
119 "--log-level=alert " +
120 "--generic=socket,out,20," + settings
.hostAddress
+ "," + QString::number(settings
.inPort
) + ",udp,opfgprotocol");
121 if (settings
.manualControlEnabled
) { // <--[BCH] What does this do? Why does it depend on ManualControl?
122 args
.append(" --generic=socket,in,400," + settings
.remoteAddress
+ "," + QString::number(settings
.outPort
) + ",udp,opfgprotocol");
125 // Start FlightGear - only if checkbox is selected in HITL options page
126 if (settings
.startSim
) {
127 QString
cmd("\"" + settings
.binPath
+ "\" " + args
+ "\n");
128 simProcess
->write(cmd
.toLatin1());
130 emit
processOutput("Start Flightgear from the command line with the following arguments: \n\n" + args
+ "\n\n" +
131 "You can optionally run Flightgear from a networked computer.\n" +
132 "Make sure the computer running Flightgear can can ping your local interface adapter. ie." + settings
.hostAddress
+ "\n"
133 "Remote computer must have the correct OpenPilot protocol installed.");
136 udpCounterGCSsend
= 0;
141 void FGSimulator::processReadyRead()
143 QByteArray bytes
= simProcess
->readAllStandardOutput();
146 if (!str
.contains("Error reading data")) { // ignore error
147 emit
processOutput(str
);
151 void FGSimulator::transmitUpdate()
153 ActuatorDesired::DataFields actData
;
154 FlightStatus::DataFields flightStatusData
= flightStatus
->getData();
155 ManualControlCommand::DataFields manCtrlData
= manCtrlCommand
->getData();
161 if (flightStatusData
.FlightMode
== FlightStatus::FLIGHTMODE_MANUAL
) {
162 // Read joystick input
163 if (flightStatusData
.Armed
== FlightStatus::ARMED_ARMED
) {
164 // Note: Pitch sign is reversed in FG ?
165 ailerons
= manCtrlData
.Roll
;
166 elevator
= -manCtrlData
.Pitch
;
167 rudder
= manCtrlData
.Yaw
;
168 throttle
= manCtrlData
.Throttle
;
171 // Read ActuatorDesired from autopilot
172 actData
= actDesired
->getData();
174 ailerons
= actData
.Roll
;
175 elevator
= -actData
.Pitch
;
176 rudder
= actData
.Yaw
;
177 throttle
= actData
.Thrust
;
180 int allowableDifference
= 10;
182 // qDebug() << "UDP sent:" << udpCounterGCSsend << " - UDP Received:" << udpCounterFGrecv;
184 if (udpCounterFGrecv
== udpCounterGCSsend
) {
185 udpCounterGCSsend
= 0;
188 if ((udpCounterGCSsend
< allowableDifference
) || (udpCounterFGrecv
== 0)) { // FG udp queue is not delayed
191 // Send update to FlightGear
193 cmd
= QString("%1,%2,%3,%4,%5\n")
194 .arg(ailerons
) // ailerons
195 .arg(elevator
) // elevator
196 .arg(rudder
) // rudder
197 .arg(throttle
) // throttle
198 .arg(udpCounterGCSsend
); // UDP packet counter delay
200 QByteArray data
= cmd
.toLatin1();
202 if (outSocket
->writeDatagram(data
, QHostAddress(settings
.remoteAddress
), settings
.outPort
) == -1) {
203 emit
processOutput("Error sending UDP packet to FG: " + outSocket
->errorString() + "\n");
206 // don't send new packet. Flightgear cannot process UDP fast enough.
207 // V1.9.1 reads udp packets at set frequency and will get delayed if packets are sent too fast
208 // V2.0 does not currently work with --generic-protocol
211 if (settings
.manualControlEnabled
) {
212 actData
.Roll
= ailerons
;
213 actData
.Pitch
= -elevator
;
214 actData
.Yaw
= rudder
;
215 actData
.Thrust
= throttle
;
216 // actData.NumLongUpdates = (float)udpCounterFGrecv;
217 // actData.UpdateTime = (float)udpCounterGCSsend;
218 actDesired
->setData(actData
);
223 void FGSimulator::processUpdate(const QByteArray
& inp
)
225 // TODO: this does not use the FLIGHT_PARAM structure, it should!
228 QStringList fields
= data
.split(",");
230 // float xRate = fields[0].toFloat() * 180.0/M_PI;
232 // float yRate = fields[1].toFloat() * 180.0/M_PI;
234 // float zRate = fields[2].toFloat() * 180.0/M_PI;
235 // Get xAccel (m/s^2)
236 float xAccel
= fields
[3].toFloat() * FT2M
;
237 // Get yAccel (m/s^2)
238 float yAccel
= fields
[4].toFloat() * FT2M
;
239 // Get xAccel (m/s^2)
240 float zAccel
= fields
[5].toFloat() * FT2M
;
242 float pitch
= fields
[6].toFloat();
243 // Get pitchRate (deg/s)
244 float pitchRate
= fields
[7].toFloat();
246 float roll
= fields
[8].toFloat();
247 // Get rollRate (deg/s)
248 float rollRate
= fields
[9].toFloat();
250 float yaw
= fields
[10].toFloat();
251 // Get yawRate (deg/s)
252 float yawRate
= fields
[11].toFloat();
253 // Get latitude (deg)
254 float latitude
= fields
[12].toFloat();
255 // Get longitude (deg)
256 float longitude
= fields
[13].toFloat();
258 // float heading = fields[14].toFloat();
260 float altitude_msl
= fields
[15].toFloat() * FT2M
;
261 // Get altitudeAGL (m)
262 float altitude_agl
= fields
[16].toFloat() * FT2M
;
263 // Get groundspeed (m/s)
264 float groundspeed
= fields
[17].toFloat() * KT2MPS
;
265 // Get airspeed (m/s)
266 float airspeed
= fields
[18].toFloat() * KT2MPS
;
267 // Get temperature (degC)
268 float temperature
= fields
[19].toFloat();
269 // Get pressure (kpa)
270 float pressure
= fields
[20].toFloat() * INHG2KPA
;
271 // Get VelocityState Down (m/s)
272 float velocityStateDown
= fields
[21].toFloat() * FPS2CMPS
* 1e-2f
;
273 // Get VelocityState East (m/s)
274 float velocityStateEast
= fields
[22].toFloat() * FPS2CMPS
* 1e-2f
;
275 // Get VelocityState Down (m/s)
276 float velocityStateNorth
= fields
[23].toFloat() * FPS2CMPS
* 1e-2f
;
278 // Get UDP packets received by FG
279 int n
= fields
[24].toInt();
281 udpCounterFGrecv
= n
;
287 memset(&out
, 0, sizeof(Output2Hardware
));
289 HomeLocation::DataFields homeData
= posHome
->getData();
290 double HomeLLA
[3] = { (double)homeData
.Latitude
* 1e-7, (double)homeData
.Longitude
* 1e-7, homeData
.Altitude
};
293 double LLA
[3] = { latitude
, longitude
, altitude_msl
};
295 Utils::CoordinateConversions().RneFromLLA(HomeLLA
, HomeRNE
);
296 Utils::CoordinateConversions().LLA2ECEF(HomeLLA
, HomeECEF
);
297 Utils::CoordinateConversions().LLA2Base(LLA
, HomeECEF
, HomeRNE
, NED
);
299 // Update GPS Position objects
300 out
.latitude
= latitude
* 1e7
;
301 out
.longitude
= longitude
* 1e7
;
302 out
.altitude
= altitude_msl
;
303 out
.agl
= altitude_agl
;
304 out
.groundspeed
= groundspeed
;
306 out
.calibratedAirspeed
= airspeed
;
309 // Update BaroSensor object
310 out
.temperature
= temperature
;
311 out
.pressure
= pressure
;
313 // Update attState object
314 out
.roll
= roll
; // roll;
315 out
.pitch
= pitch
; // pitch
316 out
.heading
= yaw
; // yaw
322 // Update VelocityState.{North,East,Down}
323 out
.velNorth
= velocityStateNorth
;
324 out
.velEast
= velocityStateEast
;
325 out
.velDown
= velocityStateDown
;
327 // Update gyroscope sensor data
328 out
.rollRate
= rollRate
;
329 out
.pitchRate
= pitchRate
;
330 out
.yawRate
= yawRate
;
332 // Update accelerometer sensor data