/** ****************************************************************************** * * @file xplanesimulator.cpp * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010. * @brief * @see The GNU Public License (GPL) Version 3 * @defgroup hitlplugin * @{ * *****************************************************************************/ /* * This program 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. * * This program 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 this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * Description of X-Plane Protocol: * * To see what data can be sended/recieved to/from X-Plane, launch X-Plane -> goto main menu * (cursor at top of main X-Plane window) -> Settings -> Data Input and Output -> Data Set. * Data Set shown all X-Plane params, * each row has four checkbox: 1st check - out to UDP; 4 check - show on screen * All the UDP messages for X-Plane have the same format, which is: * 5-character MESSAGE PROLOUGE (to indicate the type of message) * and then a DATA INPUT STRUCTURE (containing the message data that you want to send or receive) * * DATA INPUT/OUTPUT STRUCTURE is the following stuct: * * struct data_struct * { * int index; // data index, the index into the list of variables // you can output from the Data Output screen in X-Plane. * float data[8]; // the up to 8 numbers you see in the data output screen associated with that selection.. // many outputs do not use all 8, though. * }; * * For Example, update of aileron/elevon/rudder in X-Plane (11 row in Data Set) * bytes value description * [0-3] DATA message type * [4] none no matter * [5-8] 11 code of setting param(row in Data Set) * [9-41] data message data (8 float values) * total size: 41 byte * */ #include "xplanesimulator.h" #include "extensionsystem/pluginmanager.h" #include #include #include #include void TraceBuf(const char* buf,int len); XplaneSimulator::XplaneSimulator(const SimulatorSettings& params) : Simulator(params) { once = false; } XplaneSimulator::~XplaneSimulator() { } void XplaneSimulator::setupUdpPorts(const QString& host, int inPort, int outPort) { inSocket->bind(QHostAddress(host), inPort); //outSocket->bind(QHostAddress(host), outPort); once = false; } bool XplaneSimulator::setupProcess() { emit processOutput(QString("Please start X-Plane manually, and make sure it is setup to output its ") + "data to host " + settings.hostAddress + " UDP port " + QString::number(settings.inPort)); return true; } /** * update data in X-Plane simulator */ void XplaneSimulator::transmitUpdate() { //Read ActuatorDesired from autopilot ActuatorDesired::DataFields actData = actDesired->getData(); float ailerons = actData.Roll; float elevator = actData.Pitch; float rudder = actData.Yaw; float throttle = actData.Throttle*2-1.0; float none = -999; //quint32 none = *((quint32*)&tmp); // get float as 4 bytes quint32 code; QByteArray buf; QDataStream stream(&buf,QIODevice::ReadWrite); // !!! LAN byte order - Big Endian #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN stream.setByteOrder(QDataStream::LittleEndian); #endif // 11th data settings (flight con: ail/elv/rud) buf.clear(); code = 11; //quint8 header[] = "DATA"; /* stream << *((quint32*)header) << (quint8)0x30 << code << *((quint32*)&elevator) << *((quint32*)&ailerons) << *((quint32*)&rudder) << none << *((quint32*)&ailerons) << none << none << none; */ buf.append("DATA0"); buf.append(reinterpret_cast(&code), sizeof(code)); buf.append(reinterpret_cast(&elevator), sizeof(elevator)); buf.append(reinterpret_cast(&ailerons), sizeof(ailerons)); buf.append(reinterpret_cast(&rudder), sizeof(rudder)); buf.append(reinterpret_cast(&none), sizeof(none)); buf.append(reinterpret_cast(&rudder), sizeof(rudder)); buf.append(reinterpret_cast(&none), sizeof(none)); buf.append(reinterpret_cast(&none), sizeof(none)); buf.append(reinterpret_cast(&none), sizeof(none)); TraceBuf(buf.data(),41); if(outSocket->writeDatagram(buf, QHostAddress(settings.remoteHostAddress), settings.outPort) == -1) { emit processOutput("Error sending UDP packet to XPlane: " + outSocket->errorString() + "\n"); } //outSocket->write(buf); // 25th data settings (throttle command) buf.clear(); code = 25; //stream << *((quint32*)header) << (quint8)0x30 << code << *((quint32*)&throttle) << none << none // << none << none << none << none << none; buf.append("DATA0"); buf.append(reinterpret_cast(&code), sizeof(code)); buf.append(reinterpret_cast(&throttle), sizeof(throttle)); buf.append(reinterpret_cast(&throttle), sizeof(throttle)); buf.append(reinterpret_cast(&none), sizeof(none)); buf.append(reinterpret_cast(&none), sizeof(none)); buf.append(reinterpret_cast(&none), sizeof(none)); buf.append(reinterpret_cast(&none), sizeof(none)); buf.append(reinterpret_cast(&none), sizeof(none)); buf.append(reinterpret_cast(&none), sizeof(none)); if(outSocket->writeDatagram(buf, QHostAddress(settings.remoteHostAddress), settings.outPort) == -1) { emit processOutput("Error sending UDP packet to XPlane: " + outSocket->errorString() + "\n"); } //outSocket->write(buf); /** !!! this settings was given from ardupilot X-Plane.pl, I comment them but if it needed comment should be removed !!! // 8th data settings (joystick 1 ail/elv/rud) stream << "DATA0" << quint32(11) << elevator << ailerons << rudder << float(-999) << float(-999) << float(-999) << float(-999) << float(-999); outSocket->write(buf); */ } /** * process data string from X-Plane simulator */ void XplaneSimulator::processUpdate(const QByteArray& dataBuf) { float altitude = 0; float latitude = 0; float longitude = 0; float airspeed = 0; float speed = 0; float pitch = 0; float roll = 0; float heading = 0; float pressure = 0; float temperature = 0; float velX = 0; float velY = 0; float velZ = 0; float dstX = 0; float dstY = 0; float dstZ = 0; float rollRate=0; float pitchRate=0; float yawRate=0; QString str; QByteArray& buf = const_cast(dataBuf); QString data(buf); if(data.left(4) == "DATA") // check type of packet { buf.remove(0,5); if(dataBuf.size() % 36) { qxtLog->info("incorrect length of UDP packet: ",buf); return; // incorrect length of struct } // check correctness of data length, length must be multiple of (id_size+8*float_size)=4+8*4=36 int channelCounter = dataBuf.size() / 36; do { switch(buf[0]) // switch by id { case XplaneSimulator::LatitudeLongitude: latitude = *((float*)(buf.data()+4*1)); longitude = *((float*)(buf.data()+4*2)); altitude = *((float*)(buf.data()+4*3))* FT2M; break; case XplaneSimulator::Speed: airspeed = *((float*)(buf.data()+4*7)); speed = *((float*)(buf.data()+4*8)); break; case XplaneSimulator::PitchRollHeading: pitch = *((float*)(buf.data()+4*1)); roll = *((float*)(buf.data()+4*2)); heading = *((float*)(buf.data()+4*3)); break; /* case XplaneSimulator::SystemPressures: pressure = *((float*)(buf.data()+4*1)); break; */ case XplaneSimulator::AtmosphereWeather: pressure = *((float*)(buf.data()+4*1)) * INHG2KPA; temperature = *((float*)(buf.data()+4*2)); break; case XplaneSimulator::LocVelDistTraveled: dstX = *((float*)(buf.data()+4*1)); dstY = *((float*)(buf.data()+4*2)); dstZ = *((float*)(buf.data()+4*3)); velX = *((float*)(buf.data()+4*4)); velY = *((float*)(buf.data()+4*5)); velZ = *((float*)(buf.data()+4*6)); break; case XplaneSimulator::AngularVelocities: pitchRate = *((float*)(buf.data()+4*1)); rollRate = *((float*)(buf.data()+4*2)); yawRate = *((float*)(buf.data()+4*3)); break; default: break; } channelCounter--; buf.remove(0,36); } while (channelCounter); HomeLocation::DataFields homeData = posHome->getData(); if(!once) { // Upon startup, we reset the HomeLocation object to // the plane's location: memset(&homeData, 0, sizeof(HomeLocation::DataFields)); // Update homelocation homeData.Latitude = latitude * 1e7; homeData.Longitude = longitude * 1e7; homeData.Altitude = altitude; double LLA[3]; LLA[0]=latitude; LLA[1]=longitude; LLA[2]=altitude; double ECEF[3]; double RNE[9]; Utils::CoordinateConversions().RneFromLLA(LLA,(double (*)[3])RNE); for (int t=0;t<9;t++) { homeData.RNE[t]=RNE[t]; } Utils::CoordinateConversions().LLA2ECEF(LLA,ECEF); homeData.ECEF[0]=ECEF[0]*100; homeData.ECEF[1]=ECEF[1]*100; homeData.ECEF[2]=ECEF[2]*100; homeData.Be[0]=0; homeData.Be[1]=0; homeData.Be[2]=0; posHome->setData(homeData); posHome->updated(); // Initialize the initial distance initX = dstX; initY = dstY; initZ = dstZ; once=1; } // Update AltitudeActual object BaroAltitude::DataFields altActualData; memset(&altActualData, 0, sizeof(BaroAltitude::DataFields)); altActualData.Altitude = altitude; altActualData.Temperature = temperature; altActualData.Pressure = pressure; altActual->setData(altActualData); // Update attActual object AttitudeActual::DataFields attActualData; memset(&attActualData, 0, sizeof(AttitudeActual::DataFields)); attActualData.Roll = roll; //roll; attActualData.Pitch = pitch; // pitch attActualData.Yaw = heading; // Yaw // attActualData.q1 = 0; // attActualData.q2 = 0; // attActualData.q3 = 0; // attActualData.q4 = 0; attActual->setData(attActualData); // Update gps objects GPSPosition::DataFields gpsData; memset(&gpsData, 0, sizeof(GPSPosition::DataFields)); gpsData.Altitude = altitude; gpsData.Heading = heading; gpsData.Groundspeed = speed; gpsData.Latitude = latitude*1e7; gpsData.Longitude = longitude*1e7; gpsData.Satellites = 10; gpsData.Status = GPSPosition::STATUS_FIX3D; gpsPos->setData(gpsData); // Update VelocityActual.{Nort,East,Down} VelocityActual::DataFields velocityActualData; memset(&velocityActualData, 0, sizeof(VelocityActual::DataFields)); velocityActualData.North = velX*100; velocityActualData.East = velY*100; velocityActualData.Down = velZ*100; velActual->setData(velocityActualData); // Update PositionActual.{Nort,East,Down} PositionActual::DataFields positionActualData; memset(&positionActualData, 0, sizeof(PositionActual::DataFields)); positionActualData.North = (dstX-initX)*100; positionActualData.East = (dstY-initY)*100; positionActualData.Down = (dstZ-initZ)*100; posActual->setData(positionActualData); // Update AttitudeRaw object (filtered gyros only for now) AttitudeRaw::DataFields rawData; memset(&rawData, 0, sizeof(AttitudeRaw::DataFields)); rawData = attRaw->getData(); rawData.gyros[0] = rollRate; //rawData.gyros_filtered[1] = cos(DEG2RAD * roll) * pitchRate + sin(DEG2RAD * roll) * yawRate; //rawData.gyros_filtered[2] = cos(DEG2RAD * roll) * yawRate - sin(DEG2RAD * roll) * pitchRate; rawData.gyros[1] = pitchRate; rawData.gyros[2] = yawRate; attRaw->setData(rawData); } // issue manual update //attActual->updated(); //altActual->updated(); //posActual->updated(); } void TraceBuf(const char* buf,int len) { QString str; bool reminder=true; for(int i=0; i < len; i++) { if(!(i%16)) { if(i>0) { qDebug() << str; str.clear(); reminder=false; } reminder=true; } str+=QString(" 0x%1").arg((quint8)buf[i],2,16,QLatin1Char('0')); } if(reminder) qDebug() << str; }