目录
前言
上篇我们介绍QGC开头几个工具类,但是没有将QGCApplication因为看到里面类和文件很多都是还没接触到的,所以决定先看其他的。这篇我们看下源码中ADSB文件夹下的代码是干什么的。
何为ADSB,先来一段百度百科的解释,ADS-B[1]是广播式自动相关监视的英文缩写,它主要实施空对空监视,一般情况下,只需机载电子设备(GPS接收机、数据链收发机及其天线、驾驶舱冲突信息显示器CDTI),不需要任何地面辅助设备即可完成相关功能,装备了ADS-B[1]的飞机可通过数据链广播其自身的精确位置和其它数据(如速度、高度及飞机是否转弯、爬升或下降等)。ADS-B[1]接收机与空管系统、其它飞机的机载ADS-B[1]结合起来,在空地都能提供精确、实时的冲突信息。ADS-B[1] 是一种全新科技,它将当今空中交通管制中的三大要素通信、导航、监视重新定义。
Automatic——自动,“全天候运行”,无需职守
Dependent——相关,它只需要于依赖精确地全球卫星导航定位数据
Surveillance——监视,监视(获得)飞机位置、高度、速度、航向、识别号和其它信息
Broadcast——广播,无需应答,飞机之间或与地面站互相广播各自的数据信息
飞机上:
GPS接收机,GPS是ADS-B系统主要的数据来源。
静压管或大气计算机,可以向ADS-B提供气压高度信息。
FMS,可以向ADS-B提供航班号信息。
S模式应答机,从GPS、静压管或大气计算机、FMS获得信息并发送出去。
地面上:
ADS-B接收机:接收飞机上发送出来的信息并传送给显示系统。
显示系统:图形化显示飞机的位置信息。
下面来分析代码
一.ADSBVehicle
这个类时候单体类,应该与后边要讲的ADSBVehicleManager对应,属于被管理者。
首先分析头文件:
/****************************************************************************
*
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#pragma once
#include <QObject>
#include <QGeoCoordinate>
#include <QElapsedTimer>
#include "QGCMAVLink.h"
class ADSBVehicle : public QObject
{
Q_OBJECT
public:
enum {
CallsignAvailable = 1 << 1, //通讯呼号 2
LocationAvailable = 1 << 2, //定位 4
AltitudeAvailable = 1 << 3, //海拔高度 8
HeadingAvailable = 1 << 4, //航向 16
AlertAvailable = 1 << 5, //告警 32
};
typedef struct {
uint32_t icaoAddress; // Required 国际民用航空组织,国际民航组织 (International Civil Aviation Organization)
QString callsign; //通讯呼号
QGeoCoordinate location; //定位
double altitude; //海拔高度
double heading; //航向
bool alert; //告警
uint32_t availableFlags; //可用标志
} VehicleInfo_t;
//构造函数 传入vehicleInfo结构
ADSBVehicle(const VehicleInfo_t& vehicleInfo, QObject* parent);
//定义属性
//还是以上的几个属性
Q_PROPERTY(int icaoAddress READ icaoAddress CONSTANT)
Q_PROPERTY(QString callsign READ callsign NOTIFY callsignChanged)
Q_PROPERTY(QGeoCoordinate coordinate READ coordinate NOTIFY coordinateChanged)
Q_PROPERTY(double altitude READ altitude NOTIFY altitudeChanged) // NaN for not available
Q_PROPERTY(double heading READ heading NOTIFY headingChanged) // NaN for not available
Q_PROPERTY(bool alert READ alert NOTIFY alertChanged) // Collision path
int icaoAddress (void) const { return static_cast<int>(_icaoAddress); }
QString callsign (void) const { return _callsign; }
QGeoCoordinate coordinate (void) const { return _coordinate; }
double altitude (void) const { return _altitude; }
double heading (void) const { return _heading; }
bool alert (void) const { return _alert; }
void update(const VehicleInfo_t& vehicleInfo);
/// check if the vehicle is expired and should be removed
/// 检查设备是否过期,应予以删除
bool expired();
signals:
void coordinateChanged ();
void callsignChanged ();
void altitudeChanged ();
void headingChanged ();
void alertChanged ();
private:
uint32_t _icaoAddress;
QString _callsign;
QGeoCoordinate _coordinate;
double _altitude;
double _heading;
bool _alert;
//上次更新的时间
QElapsedTimer _lastUpdateTimer;
static constexpr qint64 expirationTimeoutMs = 120000; ///< timeout with no update in ms after which the vehicle is removed.车辆被移除后,在毫秒内没有更新的超时
///< AirMap sends updates for each vehicle every second. AirMap每秒钟都会更新每辆车的信息
};
Q_DECLARE_METATYPE(ADSBVehicle::VehicleInfo_t)
头文件中主要围绕的还是那么几个属性,国际民用航空组织的地址,呼号,定位,海拔高度,航向,告警信息,还有对过期设备的管理,更新。
其中Q_DECLARE_METATYPE(ADSBVehicle::VehicleInfo_t)这个宏的作用这里说下。
Q_DECLARE_METATYPE(Type)
这个宏是为了让QMetaType知道Type这个数据类型,并提供一个默认的拷贝构造函数和析构函数。QVariant需要使用Q_DECLARE_METATYPE这个宏来定制类型。
当使用这个宏的时候要求Type是一个完整的数据类型。可以使用Q_DECLARE_OPAQUE_POINTER()来注册一个指针用于转发声明类型。
一般都把这个宏放到结构体或类的末尾【注意:这是官方说的】,如果不放到末尾也是阔以的,就放到头文件中,当你用 QVariant就要包含那个.h,个人觉得这非常不适合面向对象以及模块化编程。
看下官方例子更为直观
struct MyStruct
{
int i;
...
};
Q_DECLARE_METATYPE(MyStruct)
namespace MyNamespace
{
...
}
Q_DECLARE_METATYPE(MyNamespace::MyStruct)
MyStruct s;
QVariant var;
var.setValue(s); // copy s into the variant
...
// retrieve the value
MyStruct s2 = var.value<MyStruct>();
感觉就是反射。具体深入一些讲解可以看下这篇:Qt文档阅读笔记-关于Q_DECLARE_METATYPE原理以及使用_IT1995的博客-CSDN博客_q_declare_metatype
cc文件如下
/****************************************************************************
*
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#include "ADSBVehicle.h"
#include "QGCLoggingCategory.h"
#include "QGC.h"
#include <QDebug>
#include <QtMath>
ADSBVehicle::ADSBVehicle(const VehicleInfo_t& vehicleInfo, QObject* parent)
: QObject (parent)
, _icaoAddress (vehicleInfo.icaoAddress)
, _altitude (qQNaN())
, _heading (qQNaN())
, _alert (false)
{
//构造函数上来先设置值然后更新
update(vehicleInfo);
}
void ADSBVehicle::update(const VehicleInfo_t& vehicleInfo)
{
if (_icaoAddress != vehicleInfo.icaoAddress) {
qCWarning(ADSBVehicleManagerLog) << "ICAO address mismatch expected:actual" << _icaoAddress << vehicleInfo.icaoAddress;
return;
}
if (vehicleInfo.availableFlags & CallsignAvailable) {
//不相等才设置并触发信号
if (vehicleInfo.callsign != _callsign) {
_callsign = vehicleInfo.callsign;
emit callsignChanged();
}
}
if (vehicleInfo.availableFlags & LocationAvailable) {
if (_coordinate != vehicleInfo.location) {
_coordinate = vehicleInfo.location;
emit coordinateChanged();
}
}
if (vehicleInfo.availableFlags & AltitudeAvailable) {
if (!QGC::fuzzyCompare(vehicleInfo.altitude, _altitude)) {
_altitude = vehicleInfo.altitude;
emit altitudeChanged();
}
}
if (vehicleInfo.availableFlags & HeadingAvailable) {
if (!QGC::fuzzyCompare(vehicleInfo.heading, _heading)) {
_heading = vehicleInfo.heading;
emit headingChanged();
}
}
if (vehicleInfo.availableFlags & AlertAvailable) {
if (vehicleInfo.alert != _alert) {
_alert = vehicleInfo.alert;
emit alertChanged();
}
}
_lastUpdateTimer.restart();
}
bool ADSBVehicle::expired()
{
//函数用来判断是否已经过了expirationTimeoutMs这个时间
return _lastUpdateTimer.hasExpired(expirationTimeoutMs);
}
二.ADSBVehicleManager
此文件含有两个类,一个是ADSBTCPLink这个类继承自线程,主要处理对远程服务器的tcp连接,连接异常处理,已经连接后ADSB数据的获取和解析。另一个是ADSBVehicleManager,此类主要处理各个ADSBVehicle,包括了所有的设备列表和map,对数据的清理,以及接收ADSBTCPLink发出的设备更新信号。代码如下:
/****************************************************************************
*
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#pragma once
#include "QGCToolbox.h"
#include "QmlObjectListModel.h"
#include "ADSBVehicle.h"
#include <QThread>
#include <QTcpSocket>
#include <QTimer>
#include <QGeoCoordinate>
//定义类
class ADSBVehicleManagerSettings;
//继承自thread
///ADSB的连接 tcp连接
class ADSBTCPLink : public QThread
{
Q_OBJECT
public:
//构造函数 地址 端口号
ADSBTCPLink(const QString& hostAddress, int port, QObject* parent);
~ADSBTCPLink();
signals:
//信号 设备更新和错误
void adsbVehicleUpdate(const ADSBVehicle::VehicleInfo_t vehicleInfo);
void error(const QString errorMsg);
protected:
void run(void) final;
private slots:
//读取数据
void _readBytes(void);
private:
//硬件连接
void _hardwareConnect(void);
void _parseLine(const QString& line);
QString _hostAddress;
int _port;
QTcpSocket* _socket = nullptr;
};
///ADSBVehicleManager继承自QGCTool
class ADSBVehicleManager : public QGCTool {
Q_OBJECT
public:
//构造函数
ADSBVehicleManager(QGCApplication* app, QGCToolbox* toolbox);
//定义属性adsbVehicles 设备列表
Q_PROPERTY(QmlObjectListModel* adsbVehicles READ adsbVehicles CONSTANT)
//返回adsb设备列表
QmlObjectListModel* adsbVehicles(void) { return &_adsbVehicles; }
//QGCTool overrides具体可以看我讲解QGCTool那一节
void setToolbox(QGCToolbox* toolbox) final;
//槽信号处理函数
public slots:
void adsbVehicleUpdate (const ADSBVehicle::VehicleInfo_t vehicleInfo);
void _tcpError (const QString errorMsg);
private slots:
void _cleanupStaleVehicles(void);
private:
QmlObjectListModel _adsbVehicles; //设备列表
QMap<uint32_t, ADSBVehicle*> _adsbICAOMap; //ADSBVehicle的map
QTimer _adsbVehicleCleanupTimer; //清理定时器
ADSBTCPLink* _tcpLink = nullptr; //tcp网络连接
};
cc文件如下:
/****************************************************************************
*
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#include "ADSBVehicleManager.h"
#include "QGCLoggingCategory.h"
#include "QGCApplication.h"
#include "SettingsManager.h"
#include "ADSBVehicleManagerSettings.h"
#include <QDebug>
ADSBVehicleManager::ADSBVehicleManager(QGCApplication* app, QGCToolbox* toolbox)
: QGCTool(app, toolbox)
{
}
//设置工具箱父类
void ADSBVehicleManager::setToolbox(QGCToolbox* toolbox)
{
QGCTool::setToolbox(toolbox);
//绑定槽函数处理设备的清理
connect(&_adsbVehicleCleanupTimer, &QTimer::timeout, this, &ADSBVehicleManager::_cleanupStaleVehicles);
_adsbVehicleCleanupTimer.setSingleShot(false);
_adsbVehicleCleanupTimer.start(1000);
//获取此类的setting设置
ADSBVehicleManagerSettings* settings = qgcApp()->toolbox()->settingsManager()->adsbVehicleManagerSettings();
//如果设置的adsb服务器可以连接
if (settings->adsbServerConnectEnabled()->rawValue().toBool()) {
//创建tcplink 网络地址和网络端口
_tcpLink = new ADSBTCPLink(settings->adsbServerHostAddress()->rawValue().toString(), settings->adsbServerPort()->rawValue().toInt(), this);
//创建槽函数处理设备更新和错误
connect(_tcpLink, &ADSBTCPLink::adsbVehicleUpdate, this, &ADSBVehicleManager::adsbVehicleUpdate, Qt::QueuedConnection);
connect(_tcpLink, &ADSBTCPLink::error, this, &ADSBVehicleManager::_tcpError, Qt::QueuedConnection);
}
}
void ADSBVehicleManager::_cleanupStaleVehicles()
{
// Remove all expired ADSB vehicles移除所有过期的ADSB设备
for (int i=_adsbVehicles.count()-1; i>=0; i--) {
ADSBVehicle* adsbVehicle = _adsbVehicles.value<ADSBVehicle*>(i);
if (adsbVehicle->expired()) {
qCDebug(ADSBVehicleManagerLog) << "Expired" << QStringLiteral("%1").arg(adsbVehicle->icaoAddress(), 0, 16);
_adsbVehicles.removeAt(i);
_adsbICAOMap.remove(adsbVehicle->icaoAddress());
adsbVehicle->deleteLater();
}
}
}
void ADSBVehicleManager::adsbVehicleUpdate(const ADSBVehicle::VehicleInfo_t vehicleInfo)
{
uint32_t icaoAddress = vehicleInfo.icaoAddress;
//更细操作如果包含就行找到进行更新
//如果不包含并且定位功能可用就创建ADSBVehicle然后插入到容器中
if (_adsbICAOMap.contains(icaoAddress)) {
_adsbICAOMap[icaoAddress]->update(vehicleInfo);
} else {
if (vehicleInfo.availableFlags & ADSBVehicle::LocationAvailable) {
ADSBVehicle* adsbVehicle = new ADSBVehicle(vehicleInfo, this);
_adsbICAOMap[icaoAddress] = adsbVehicle;
_adsbVehicles.append(adsbVehicle);
}
}
}
void ADSBVehicleManager::_tcpError(const QString errorMsg)
{
qgcApp()->showAppMessage(tr("ADSB Server Error: %1").arg(errorMsg));
}
ADSBTCPLink::ADSBTCPLink(const QString& hostAddress, int port, QObject* parent)
: QThread (parent)
, _hostAddress (hostAddress)
, _port (port)
{
//构造函数中开启线程
moveToThread(this);
start();
}
ADSBTCPLink::~ADSBTCPLink(void)
{
//析构函数处理数据清理关闭socket
if (_socket) {
QObject::disconnect(_socket, &QTcpSocket::readyRead, this, &ADSBTCPLink::_readBytes);
_socket->disconnectFromHost();
_socket->deleteLater();
_socket = nullptr;
}
quit();
wait();
}
void ADSBTCPLink::run(void)
{
_hardwareConnect();
exec();
}
void ADSBTCPLink::_hardwareConnect()
{
_socket = new QTcpSocket();
QObject::connect(_socket, &QTcpSocket::readyRead, this, &ADSBTCPLink::_readBytes);
_socket->connectToHost(_hostAddress, static_cast<quint16>(_port));
// Give the socket a second to connect to the other side otherwise error out
// 给套接字一秒钟时间连接到另一边,否则会出错
if (!_socket->waitForConnected(1000)) {
qCDebug(ADSBVehicleManagerLog) << "ADSB Socket failed to connect";
emit error(_socket->errorString());
delete _socket;
_socket = nullptr;
return;
}
qCDebug(ADSBVehicleManagerLog) << "ADSB Socket connected";
}
void ADSBTCPLink::_readBytes(void)
{
if (_socket) {
QByteArray bytes = _socket->readLine();
_parseLine(QString::fromLocal8Bit(bytes));
}
}
///此函数处理adsb传递过来的数据
void ADSBTCPLink::_parseLine(const QString& line)
{
//传输数据以MSG开头
if (line.startsWith(QStringLiteral("MSG"))) {
qCDebug(ADSBVehicleManagerLog) << "ADSB SBS-1" << line;
//以逗号分割
QStringList values = line.split(QStringLiteral(","));
if (values[1] == QStringLiteral("3")) {
bool icaoOk, altOk, latOk, lonOk;
//16进制获取空管地址
uint32_t icaoAddress = values[4].toUInt(&icaoOk, 16);
//获取海拔
int modeCAltitude = values[11].toInt(&altOk);
//获取经度
double lat = values[14].toDouble(&latOk);
//获取维度
double lon = values[15].toDouble(&lonOk);
//获取呼号
QString callsign = values[10];
//处理错误情况
if (!icaoOk || !altOk || !latOk || !lonOk) {
return;
}
if (lat == 0 && lon == 0) {
return;
}
//计算高度
double altitude = modeCAltitude / 3.048;
//构造地理坐标
QGeoCoordinate location(lat, lon);
//构建结构起
ADSBVehicle::VehicleInfo_t adsbInfo;
adsbInfo.icaoAddress = icaoAddress;
adsbInfo.callsign = callsign;
adsbInfo.location = location;
adsbInfo.altitude = altitude;
adsbInfo.availableFlags = ADSBVehicle::CallsignAvailable | ADSBVehicle::LocationAvailable | ADSBVehicle::AltitudeAvailable;
//触发更新信号给manager
emit adsbVehicleUpdate(adsbInfo);
} else if (values[1] == QStringLiteral("4")) {
bool icaoOk, headingOk;
//16进制获取空管地址
uint32_t icaoAddress = values[4].toUInt(&icaoOk, 16);
//获取航向
double heading = values[13].toDouble(&headingOk);
if (!icaoOk || !headingOk) {
return;
}
ADSBVehicle::VehicleInfo_t adsbInfo;
adsbInfo.icaoAddress = icaoAddress;
adsbInfo.heading = heading;
adsbInfo.availableFlags = ADSBVehicle::HeadingAvailable;
emit adsbVehicleUpdate(adsbInfo);
} else if (values[1] == QStringLiteral("1")) {
bool icaoOk;
//16进制获取空管地址
uint32_t icaoAddress = values[4].toUInt(&icaoOk, 16);
if (!icaoOk) {
return;
}
ADSBVehicle::VehicleInfo_t adsbInfo;
adsbInfo.icaoAddress = icaoAddress;
adsbInfo.callsign = values[10];
adsbInfo.availableFlags = ADSBVehicle::CallsignAvailable;
emit adsbVehicleUpdate(adsbInfo);
}
}
}
总结
这篇文章篇幅较短,主要介绍了ADSB系统,以及ADSB在GGC中是如何处理的,主要模块有三个一个是vehicle是ADSB设备单体,一个是link处理与远程服务器的连接,一个是manger管理所有的vehicle。还有一个setting类因为在setting文件夹下并且继承了setting的某些类所以准备放到讲解setting的时候在讲解。
下篇我们按照顺序对Aimap文件夹下的代码进行解析。