| |

CityMaker

CityMaker的3D产品

  • TED:三维地形数据,用高程(DEM)、影像(DOM)TerraiPush软件生产。大文件格式为*.ted,支持MongoDB存储。是TerrainPush生成的。
  • FDB:地理特征数据库,能够管理三维模型、矢量、点云类型数据,三维模型可以导入手工建模模型或BIM模型,以及其他平台的模型,如GDB、shape+X等。大文件格式为*.fdb、支持Oracle、Mysql、SQL Server、GBase等数据库。是CityMaker Builder生成的。
  • TDB:瓦片数据,一种是OSGB数据转为TDB,一种为FDB通过瓦片化处理生产为TDB数据。是TilePush发布生成,或osgb通过CityMaker Builder转换生成。
    TDB和FDB可以进行相互转化,也就意味着,TDB(OSGB)数据可以编辑、切割,实现对象单体化。

CityMaker支持的数据格式:

1
2
3
4
5
6
7
数据类型	数据格式
二维矢量 shape file、DWG、dxf、Kml、ArcSDE、WFS、 Oracle Spatial、Microsoft SQLServer Spatial、PostGIS、MapServer
栅格数据 img、tif、.jpg、 jp2、j2k、bmp、sid、ecw、 dem 、 WMS、WMTS、MapServer、Oracle GeoRaster
三维数据 X file、.3ds、.dae、SKP 、FBX 、Dwg、obj、OSG、IFC、rvt、dgn
贴 图 jpg、bmp、dds、jpeg/jpg、png、tga、gif、tif、PVR、CubeMap
点 云 Las、ply
瓦 片 OSGB、TDB

内嵌pdf测试

CSharp读取Xml文件

xml 读取

来源:ctyMaker:/MotionPath

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private void LoadMotionPathAndLineFromFile()
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(Path.Combine(Application.StartupPath.Substring(0, flag), @"Samples\CSharp\bin\MotionPath.xml"));
wkt = xmlDoc.SelectSingleNode("root/WKT").InnerText;
// 指定坐标系与xml里的相同
motionPath.CrsWKT = xmlDoc.SelectSingleNode("root/WKT").InnerText;
point.SpatialCRS = new CRSFactory().CreateFromWKT(motionPath.CrsWKT) as ISpatialCRS;
line.SpatialCRS = new CRSFactory().CreateFromWKT(motionPath.CrsWKT) as ISpatialCRS;

XmlNodeList nodes = xmlDoc.SelectNodes("root/Waypoint");
int i = 0;
foreach (XmlNode node in nodes)
{
double x = double.Parse(node.SelectSingleNode("X").InnerText);
double y = double.Parse(node.SelectSingleNode("Y").InnerText);
double z = double.Parse(node.SelectSingleNode("Z").InnerText);
double heading = double.Parse(node.SelectSingleNode("Heading").InnerText);
double tilt = double.Parse(node.SelectSingleNode("Tilt").InnerText);
double roll = double.Parse(node.SelectSingleNode("Roll").InnerText);
double when = double.Parse(node.SelectSingleNode("When").InnerText);
position.Set(x, y, z);
point.Position = position;
if (line.PointCount == 0)
{
line.StartPoint = point;
}
else
line.AddPointAfter(i - 1, point);
i++;
angle.Set(heading, tilt, roll);
scale.Set(1, 1, 1);
motionPath.AddWaypoint2(point, angle, scale, when);
this.axRenderControl1.ObjectManager.CreateRenderPoint(point, null, rootId);
}

ICurveSymbol cur = new CurveSymbol();
cur.Color = 0xFFC0C0C0;
cur.Width = -2;
rline = this.axRenderControl1.ObjectManager.CreateRenderPolyline(line, cur, rootId);
}

QGroundControl笔记 —— 自定义 MAVLink 消息

参考:http://www.mavlink.org/dev/mavlink_groundcontrol_integration_tutorial

自定义 MAVLink 消息

MAVLink 官方提供消息生成工具(MAVLink Generator (C/C++, Python)),用于自定义Mavlink扩展。MAVLink官方库地址:url = https://github.com/mavlink/mavlink.git

其中,mavgenerate.py 就是 Python GUI 的MAVLink消息生成工具。【需安装 Python 2.7

这里写图片描述

MAVLink 消息定义由 .xml 文件完成,可根据库文件中提供的示例完成,路径:mavlink\message_definitions\v1.0

此处自定义消息时,需在官网处文档,查询已占用的Message_ID,避免出现冲突。完成自定义消息后,需通过官方工具将XML 文档编译为 c 代码。

其中,输出路径最好是一个独立的文件夹,因为输出的文件很多。。。

对自定义消息不熟练的同学,建议直接对完整的 XML 文档进行修改。
MAVLink 1.0 的 XML 文档: 传送门

将生成的 MAVLink 库,放入 libs/mavlink 目录中。

地面站解析

官方的文档中描述了 v2.9.6 及以前版本的修改方法,从 UASUASInterface 继承一个新类,并重写其 receiveMessage() 函数,用于处理 MAVLink 消息。

此处,由于 在 v3.0 版中,对代码重构,代码结构改变较大。所以,在此建议,直接在 UAS 类 的 receiveMessage() 函数中进行修改。

QGroundControl笔记 —— 界面启动

界面启动描述

QGroundControl 的启动由 main() 函数开始,调用 QGCApplication::_initForNormalAppBoot() 再由 MainWindow::_create() 调用 MainWindow() 构造函数,完成界面的启动。

界面的启动由主界面读入 QML 源文档开始:"qrc:qml/MainWindowHybrid.qml"

主要相关文件

\src\ui\MainWindowHybrid.qml
\src\ui\MainWindowInner.qml
\src\MissionEditor\MissionEditor.qml
\src\VehicleSetup\SetupView.qml
\src\ui\MainWindowLeftPanel.qml
\src\FlightDisplay\FlightDisplayView.qml
\src\ui\toolbar\MainToolBar.qml
\src\ui\toolbar\MainToolBarIndicators.qml

MainWindowHybrid.qml

首先,载入目标界面文件:

1
2
3
4
5
6
7
8
9
10
11
Loader {
id: mainWindowInner
anchors.fill: parent
source: "MainWindowInner.qml"

Connections {
target: mainWindowInner.item

onReallyClose: controller.reallyClose()
}
}

执行界面显示:

1
2
3
4
5
6
7
8
9
10
11
12
function showFlyView() 
function showPlanView()
function showSetupView()
function attemptWindowClose()

// The following are use for unit testing only

function showSetupFirmware()
function showSetupParameters()
function showSetupSummary()
function showSetupVehicleComponent(vehicleComponent)
function showMessage(message)

MainWindowInner.qml

这里的内容较为驳杂,只介绍界面显示相关的内容。

建议阅读 QML 文档之前,首先了解 Qt Quick 的相关内容,或者有 JavaScript 开发经验。

首先,定义了几个主界面的源文档位置:

1
2
3
readonly property string _planViewSource:       "MissionEditor.qml"
readonly property string _setupViewSource: "SetupView.qml"
readonly property string _preferencesSource: "MainWindowLeftPanel.qml"

currentPopUp 是主界面中的临时界面,如消息提示框等,再刷新界面时,自动消失。

几大界面的切换函数(以显示飞行界面为例):

1
2
3
4
5
preferencesPanel.visible    = false
flightView.visible = true
setupViewLoader.visible = false
planViewLoader.visible = false
toolBar.checkFlyButton()

其中,飞行界面(flightView)定义于 \src\FlightDisplay\FlightDisplayView.qml

MainToolBar

这里写图片描述

工具栏采用 横向排列(Row) 布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Row {
id: viewRow
height: mainWindow.tbCellHeight
spacing: mainWindow.tbSpacing
anchors.left: parent.left
anchors.bottomMargin: 1
anchors.top: parent.top
anchors.bottom: parent.bottom

ExclusiveGroup { id: mainActionGroup }

QGCToolBarButton {
id: preferencesButton
width: mainWindow.tbButtonWidth
anchors.top: parent.top
anchors.bottom: parent.bottom
exclusiveGroup: mainActionGroup
source: "/res/QGCLogoWhite"
logo: true
onClicked: toolBar.showPreferences()
}

QGCToolBarButton {
id: setupButton
width: mainWindow.tbButtonWidth
anchors.top: parent.top
anchors.bottom: parent.bottom
exclusiveGroup: mainActionGroup
source: "/qmlimages/Gears.svg"
onClicked: toolBar.showSetupView()
}

QGCToolBarButton {
id: planButton
width: mainWindow.tbButtonWidth
anchors.top: parent.top
anchors.bottom: parent.bottom
exclusiveGroup: mainActionGroup
source: "/qmlimages/Plan.svg"
onClicked: toolBar.showPlanView()
}

QGCToolBarButton {
id: flyButton
width: mainWindow.tbButtonWidth
anchors.top: parent.top
anchors.bottom: parent.bottom
exclusiveGroup: mainActionGroup
source: "/qmlimages/PaperPlane.svg"
onClicked: toolBar.showFlyView()
}
}

工具栏右侧分别是:GPS 信息、RC 、数传、电池、飞行模式;这些指示符显示,定义于文件:MainToolBarIndicators.qml

那条长长的进度条。。。。

1
2
3
4
5
6
7
Rectangle {
id: progressBar
anchors.bottom: parent.bottom
height: toolBar.height * 0.05
width: parent.width * _controller.progressBarValue
color: colorGreen
}

Mavlink地面站协议分析

Pixhawk/APM都是采用MAVLINK协议实现的飞控的数据链路传输。先简单介绍下mavlink协议。Mavlink协议最早由 苏黎世联邦理工学院 计算机视觉与几何实验组 的 Lorenz Meier于2009年发布,并遵循LGPL开源协议。Mavlink协议是在串口通讯基础上的一种更高层的开源通讯协议,主要应用在微型飞行器(micro aerial vehicle)的通讯上。Mavlink是为小型飞行器和地面站(或者其他飞行器)通讯时常常用到的那些数据制定一种发送和接收的规则并加入了校验(checksum)功能。据说亿航四轴的初期版本就是参考的MAVLINK协议或者说参考的APM飞控系统。

QGroundControl笔记 —— 飞控自动连接

在 PX4 中可以在软件设置中设置是否启用自动连接

这里写图片描述

QML 通过 Q_PROPERTY 宏,将属性注册到元对象系统中,实现与C++的交互【详见:http://blog.csdn.net/luoshi006/article/details/52353821】

1
Q_PROPERTY(bool     usbDirect       READ usbDirect          WRITE setUsbDirect          NOTIFY usbDirectChanged)        ///< true: direct usb connection to board

此处实现复选框与属性 usbDirect 的交互。

在系统初始化【QGCToolbox】时,LinkManager 设置定时器,定时检测自动连接:

1
2
3
4
5
6
void LinkManager::setToolbox(QGCToolbox *toolbox)
{
......
connect(&_portListTimer, &QTimer::timeout, this, &LinkManager::_updateAutoConnectLinks);
_portListTimer.start(_autoconnectUpdateTimerMSecs); // timeout must be long enough to get past bootloader on second pass
}

默认1000毫秒检测执行一次,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void LinkManager::_updateAutoConnectLinks(void)
{
.....
case QGCSerialPortInfo::BoardTypePX4FMUV4:
if (_autoconnectPixhawk) {
pSerialConfig = new SerialConfiguration(QString("Pixhawk on %1").arg(portInfo.portName().trimmed()));
pSerialConfig->setUsbDirect(true);
}
break;

....
....
if (pSerialConfig) {
pSerialConfig->setBaud(boardType == QGCSerialPortInfo::BoardTypeSikRadio ? 57600 : 115200);
.... _autoconnectConfigurations.append(pSerialConfig);
createConnectedLink(pSerialConfig);
}
....
}

两次跳转,调用 createConnectedLink()

1
2
3
4
5
6
7
8
9
10
LinkInterface* LinkManager::createConnectedLink(LinkConfiguration* config)
{
....
if (serialConfig->usbDirect()) {
_activeLinkCheckList.append((SerialLink*)pLink);
if (!_activeLinkCheckTimer.isActive()) {
_activeLinkCheckTimer.start();
}
....
}

至此,完成启动过程。

QGroundControl笔记 —— 发送 MAVLink 消息

#消息发送函数

QGroundControl 中发送 MAVLink 的命令主要被封装在 Vehicle 类中。

1
2
3
4
5
6
7
8
9
10
11
/// Sends a message to the specified link
/// @return true: message sent, false: Link no longer connected
bool sendMessageOnLink(LinkInterface* link, mavlink_message_t message);

/// Sends a message to the priority link
/// @return true: message sent, false: Link no longer connected
bool sendMessageOnPriorityLink(mavlink_message_t message) { return sendMessageOnLink(priorityLink(), message); }

/// Sends the specified messages multiple times to the vehicle in order to attempt to
/// guarantee that it makes it to the vehicle.
void sendMessageMultiple(mavlink_message_t message);

在 MAVLinkProtocol 中封装的发送消息函数:

1
2
3
void _sendMessage(mavlink_message_t message);
void _sendMessage(LinkInterface* link, mavlink_message_t message);
void _sendMessage(LinkInterface* link, mavlink_message_t message, quint8 systemid, quint8 componentid);

#消息函数使用范围

Vehicle 类中:

sendMessageOnLink,主要用于大量数据发送,包括:航点、参数、日志消息。

sendMessageOnPriorityLink,主要用于短消息传输,包括:应答、参数设置、飞行控制、飞控设置、校准等。

sendMessageMultiple,主要用于请求飞控版本号、发送数据请求等重要信息。

MAVLinkProtocol 类中的消息发送函数为私有方法,其调用主要功能是在 receiveBytes() 中,对需要反馈的消息进行回应。

示例

刷新所有参数:

1
2
3
4
5
6
void ParameterLoader::refreshAllParameters(uint8_t componentID)
{
....
mavlink_message_t msg;
mavlink_msg_param_request_list_pack(mavlink->getSystemId(), mavlink->getComponentId(), &msg, _vehicle->id(), componentID);
_vehicle->sendMessageOnLink(_vehicle->priorityLink(), msg);

读取参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void ParameterLoader::_readParameterRaw(int componentId, const QString& paramName, int paramIndex)
{
mavlink_message_t msg;
char fixedParamName[MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN];

strncpy(fixedParamName, paramName.toStdString().c_str(), sizeof(fixedParamName));
mavlink_msg_param_request_read_pack(_mavlink->getSystemId(), // Our system id
_mavlink->getComponentId(), // Our component id
&msg, // Pack into this mavlink_message_t
_vehicle->id(), // Target system id
componentId, // Target component id
fixedParamName, // Named parameter being requested
paramIndex); // Parameter index being requested, -1 for named
_vehicle->sendMessageOnLink(_vehicle->priorityLink(), msg);
}

写入参数:

1
2
3
4
5
6
7
void ParameterLoader::_writeParameterRaw(int componentId, const QString& paramName, const QVariant& value)
{
...
mavlink_message_t msg;
mavlink_msg_param_set_encode(_mavlink->getSystemId(), _mavlink->getComponentId(), &msg, &p);
_vehicle->sendMessageOnLink(_vehicle->priorityLink(), msg);
}

保存到静态存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void ParameterLoader::_saveToEEPROM(void)
{
if (_saveRequired) {
_saveRequired = false;
if (_vehicle->firmwarePlugin()->isCapable(FirmwarePlugin::MavCmdPreflightStorageCapability)) {
mavlink_message_t msg;
mavlink_msg_command_long_pack(_mavlink->getSystemId(), _mavlink->getComponentId(), &msg, _vehicle->id(), 0, MAV_CMD_PREFLIGHT_STORAGE, 1, 1, -1, -1, -1, 0, 0, 0);
_vehicle->sendMessageOnLink(_vehicle->priorityLink(), msg);
qCDebug(ParameterLoaderLog) << "_saveToEEPROM";
} else {
qCDebug(ParameterLoaderLog) << "_saveToEEPROM skipped due to FirmwarePlugin::isCapable";
}
}
}

##sendMessageOnPriorityLink

sendMessageOnPriorityLink 的用例代码较多,以校准传感器消息为例讲解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
void UAS::startCalibration(UASInterface::StartCalibrationType calType)
{
if (!_vehicle) {
return;
}

int gyroCal = 0;
int magCal = 0;
int airspeedCal = 0;
int radioCal = 0;
int accelCal = 0;
int escCal = 0;

switch (calType) {
case StartCalibrationGyro:
gyroCal = 1;
break;
case StartCalibrationMag:
magCal = 1;
break;
case StartCalibrationAirspeed:
airspeedCal = 1;
break;
case StartCalibrationRadio:
radioCal = 1;
break;
case StartCalibrationCopyTrims:
radioCal = 2;
break;
case StartCalibrationAccel:
accelCal = 1;
break;
case StartCalibrationLevel:
accelCal = 2;
break;
case StartCalibrationEsc:
escCal = 1;
break;
case StartCalibrationUavcanEsc:
escCal = 2;
break;
case StartCalibrationCompassMot:
airspeedCal = 1; // ArduPilot, bit of a hack
break;
}

mavlink_message_t msg;
mavlink_msg_command_long_pack(mavlink->getSystemId(),
mavlink->getComponentId(),
&msg,
uasId,
_vehicle->defaultComponentId(), // target component
MAV_CMD_PREFLIGHT_CALIBRATION, // command id
0, // 0=first transmission of command
gyroCal, // gyro cal
magCal, // mag cal
0, // ground pressure
radioCal, // radio cal
accelCal, // accel cal
airspeedCal, // PX4: airspeed cal, ArduPilot: compass mot
escCal); // esc cal
_vehicle->sendMessageOnPriorityLink(msg);
}

以校准水平为例,开始校准以后,通过关联信号槽,截取飞行器发送的状态消息。

1
connect(_uas, &UASInterface::textMessageReceived, this, &SensorsComponentController::_handleUASTextMessage);

截取状态信息并更新状态显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
void SensorsComponentController::_handleUASTextMessage(int uasId, int compId, int severity, QString text)
{
Q_UNUSED(compId);
Q_UNUSED(severity);

UASInterface* uas = _autopilot->vehicle()->uas();
Q_ASSERT(uas);
if (uasId != uas->getUASID()) {
return;
}

if (text.contains("progress <")) {
QString percent = text.split("<").last().split(">").first();
bool ok;
int p = percent.toInt(&ok);
if (ok) {
Q_ASSERT(_progressBar);
_progressBar->setProperty("value", (float)(p / 100.0));
}
return;
}

_appendStatusLog(text);
qCDebug(SensorsComponentControllerLog) << text;

if (_unknownFirmwareVersion) {
// We don't know how to do visual cal with the version of firwmare
return;
}

// All calibration messages start with [cal]
QString calPrefix("[cal] ");
if (!text.startsWith(calPrefix)) {
return;
}
text = text.right(text.length() - calPrefix.length());

QString calStartPrefix("calibration started: ");
if (text.startsWith(calStartPrefix)) {
text = text.right(text.length() - calStartPrefix.length());

// Split version number and cal type
QStringList parts = text.split(" ");
if (parts.count() != 2 && parts[0].toInt() != _supportedFirmwareCalVersion) {
_unknownFirmwareVersion = true;
QString msg = "Unsupported calibration firmware version, using log";
_appendStatusLog(msg);
qDebug() << msg;
return;
}

//开始校准准备,并将进度条置零;
_startVisualCalibration();

text = parts[1];
if (text == "accel" || text == "mag" || text == "gyro") {
// Reset all progress indication
_orientationCalDownSideDone = false;
_orientationCalUpsideDownSideDone = false;
_orientationCalLeftSideDone = false;
_orientationCalRightSideDone = false;
_orientationCalTailDownSideDone = false;
_orientationCalNoseDownSideDone = false;
_orientationCalDownSideInProgress = false;
_orientationCalUpsideDownSideInProgress = false;
_orientationCalLeftSideInProgress = false;
_orientationCalRightSideInProgress = false;
_orientationCalNoseDownSideInProgress = false;
_orientationCalTailDownSideInProgress = false;

// Reset all visibility
_orientationCalDownSideVisible = false;
_orientationCalUpsideDownSideVisible = false;
_orientationCalLeftSideVisible = false;
_orientationCalRightSideVisible = false;
_orientationCalTailDownSideVisible = false;
_orientationCalNoseDownSideVisible = false;

_orientationCalAreaHelpText->setProperty("text", "Place your vehicle into one of the Incomplete orientations shown below and hold it still");

if (text == "accel") {
_accelCalInProgress = true;
_orientationCalDownSideVisible = true;
_orientationCalUpsideDownSideVisible = true;
_orientationCalLeftSideVisible = true;
_orientationCalRightSideVisible = true;
_orientationCalTailDownSideVisible = true;
_orientationCalNoseDownSideVisible = true;
} else if (text == "mag") {

// Work out what the autopilot is configured to
int sides = 0;

if (_autopilot->parameterExists(FactSystem::defaultComponentId, "CAL_MAG_SIDES")) {
// Read the requested calibration directions off the system
sides = _autopilot->getParameterFact(FactSystem::defaultComponentId, "CAL_MAG_SIDES")->rawValue().toFloat();
} else {
// There is no valid setting, default to all six sides
sides = (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | (1 << 0);
}

_magCalInProgress = true;
_orientationCalTailDownSideVisible = ((sides & (1 << 0)) > 0);
_orientationCalNoseDownSideVisible = ((sides & (1 << 1)) > 0);
_orientationCalLeftSideVisible = ((sides & (1 << 2)) > 0);
_orientationCalRightSideVisible = ((sides & (1 << 3)) > 0);
_orientationCalUpsideDownSideVisible = ((sides & (1 << 4)) > 0);
_orientationCalDownSideVisible = ((sides & (1 << 5)) > 0);
} else if (text == "gyro") {
_gyroCalInProgress = true;
_orientationCalDownSideVisible = true;
} else {
Q_ASSERT(false);
}
//在 QML 界面显示单侧完成信息;
emit orientationCalSidesDoneChanged();
//在 QML 界面显示状态变换;
emit orientationCalSidesVisibleChanged();
//在 QML 界面显示进度信息;
emit orientationCalSidesInProgressChanged();

_updateAndEmitShowOrientationCalArea(true);
}
return;
}

if (text.endsWith("orientation detected")) {
QString side = text.section(" ", 0, 0);
qDebug() << "Side started" << side;

if (side == "down") {
_orientationCalDownSideInProgress = true;
if (_magCalInProgress) {
_orientationCalDownSideRotate = true;
}
} else if (side == "up") {
_orientationCalUpsideDownSideInProgress = true;
if (_magCalInProgress) {
_orientationCalUpsideDownSideRotate = true;
}
} else if (side == "left") {
_orientationCalLeftSideInProgress = true;
if (_magCalInProgress) {
_orientationCalLeftSideRotate = true;
}
} else if (side == "right") {
_orientationCalRightSideInProgress = true;
if (_magCalInProgress) {
_orientationCalRightSideRotate = true;
}
} else if (side == "front") {
_orientationCalNoseDownSideInProgress = true;
if (_magCalInProgress) {
_orientationCalNoseDownSideRotate = true;
}
} else if (side == "back") {
_orientationCalTailDownSideInProgress = true;
if (_magCalInProgress) {
_orientationCalTailDownSideRotate = true;
}
}

if (_magCalInProgress) {
_orientationCalAreaHelpText->setProperty("text", "Rotate the vehicle continuously as shown in the diagram until marked as Completed");
} else {
_orientationCalAreaHelpText->setProperty("text", "Hold still in the current orientation");
}

emit orientationCalSidesInProgressChanged();
emit orientationCalSidesRotateChanged();
return;
}

if (text.endsWith("side done, rotate to a different side")) {
QString side = text.section(" ", 0, 0);
qDebug() << "Side finished" << side;

if (side == "down") {
_orientationCalDownSideInProgress = false;
_orientationCalDownSideDone = true;
_orientationCalDownSideRotate = false;
} else if (side == "up") {
_orientationCalUpsideDownSideInProgress = false;
_orientationCalUpsideDownSideDone = true;
_orientationCalUpsideDownSideRotate = false;
} else if (side == "left") {
_orientationCalLeftSideInProgress = false;
_orientationCalLeftSideDone = true;
_orientationCalLeftSideRotate = false;
} else if (side == "right") {
_orientationCalRightSideInProgress = false;
_orientationCalRightSideDone = true;
_orientationCalRightSideRotate = false;
} else if (side == "front") {
_orientationCalNoseDownSideInProgress = false;
_orientationCalNoseDownSideDone = true;
_orientationCalNoseDownSideRotate = false;
} else if (side == "back") {
_orientationCalTailDownSideInProgress = false;
_orientationCalTailDownSideDone = true;
_orientationCalTailDownSideRotate = false;
}

_orientationCalAreaHelpText->setProperty("text", "Place you vehicle into one of the orientations shown below and hold it still");

emit orientationCalSidesInProgressChanged();
emit orientationCalSidesDoneChanged();
emit orientationCalSidesRotateChanged();
return;
}

if (text.endsWith("side already completed")) {
_orientationCalAreaHelpText->setProperty("text", "Orientation already completed, place you vehicle into one of the incomplete orientations shown below and hold it still");
return;
}

QString calCompletePrefix("calibration done:");
if (text.startsWith(calCompletePrefix)) {
//校准完成,解除飞行器状态消息截取;设置进度条状态,并完成;
_stopCalibration(StopCalibrationSuccess);
return;
}

if (text.startsWith("calibration cancelled")) {
_stopCalibration(_waitingForCancel ? StopCalibrationCancelled : StopCalibrationFailed);
return;
}

if (text.startsWith("calibration failed")) {
_stopCalibration(StopCalibrationFailed);
return;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void SensorsComponentController::_stopCalibration(SensorsComponentController::StopCalibrationCode code)
{
//解除消息截取;
disconnect(_uas, &UASInterface::textMessageReceived, this, &SensorsComponentController::_handleUASTextMessage);

//恢复按钮状态;
_compassButton->setEnabled(true);
_gyroButton->setEnabled(true);
_accelButton->setEnabled(true);
_airspeedButton->setEnabled(true);
_levelButton->setEnabled(true);
_setOrientationsButton->setEnabled(true);
_cancelButton->setEnabled(false);

if (code == StopCalibrationSuccess) {
_resetInternalState();

_progressBar->setProperty("value", 1);
} else {
_progressBar->setProperty("value", 0);
}

_waitingForCancel = false;
emit waitingForCancelChanged();

_refreshParams();

//根据返回的信息状态,更新屏幕显示;
switch (code) {
case StopCalibrationSuccess:
_orientationCalAreaHelpText->setProperty("text", "Calibration complete");
emit resetStatusTextArea();
if (_magCalInProgress) {
emit setCompassRotations();
}
break;

case StopCalibrationCancelled:
emit resetStatusTextArea();
_hideAllCalAreas();
break;

default:
// Assume failed
_hideAllCalAreas();
qgcApp()->showMessage("Calibration failed. Calibration log will be displayed.");
break;
}

_magCalInProgress = false;
_accelCalInProgress = false;
_gyroCalInProgress = false;
}

QGroundControl笔记 —— 串口数据流

地面站与飞行器的链接常用的就是:串口(Serial Link)。

本文主要描述数据经由串口读取后的流向。

相关文档

涉及到的主要文档包括:
\src\comm\SerialLink.h
\src\comm\LinkInterface.h
\src\comm\LinkManager.h
\src\comm\MAVLinkProtocol.h
\src\ui\MAVLinkDecoder.h
\src\uas\UASMessageHandler.h
\src\uas\UAS.h
\src\Vehicle\Vehicle.h

#串口数据流

当串口缓冲区有数据时,进行读串口操作,通过与 readyRead 信号关联槽函数实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//    \src\comm\SerialLink.cc
QObject::connect(_port, &QIODevice::readyRead, this, &SerialLink::_readBytes);

// \src\comm\SerialLink.cc
void SerialLink::_readBytes(void)
{
qint64 byteCount = _port->bytesAvailable();
if (byteCount) {
QByteArray buffer;
buffer.resize(byteCount);
_port->read(buffer.data(), buffer.size());
emit bytesReceived(this, buffer);//@@@@@@@@@@@@@@
}
}

在槽函数中,再次发送信号 bytesReceived() ,该信号定义于 LinkInterface 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   /**        \src\comm\LinkInterface.h
* @brief New data arrived
*
* The new data is contained in the QByteArray data. The data is copied for each
* receiving protocol. For high-speed links like image transmission this might
* affect performance, for control links it is however desirable to directly
* forward the link data.
*
* @param data the new bytes
*/
void bytesReceived(LinkInterface* link, QByteArray data);


// \src\comm\LinkManager.cc
connect(link, &LinkInterface::bytesReceived, _mavlinkProtocol, &MAVLinkProtocol::receiveBytes);

bytesReceived 信号直接关联到 MAVLinkProtocol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**   		\src\comm\MAVLinkProtocol.cc
* This method parses all incoming bytes and constructs a MAVLink packet.
* It can handle multiple links in parallel, as each link has it's own buffer/
* parsing state machine.
* @param link The interface to read from
* @see LinkInterface
**/
void MAVLinkProtocol::receiveBytes(LinkInterface* link, QByteArray b)
{
...
//检测到以下命令,则直接反馈;
MAVLINK_MSG_ID_PING
MAVLINK_MSG_ID_RADIO_STATUS
MAVLINK_MSG_ID_HEARTBEAT


// The packet is emitted as a whole, as it is only 255 - 261 bytes short
// kind of inefficient, but no issue for a groundstation pc.
// 之前的消息都是短消息,这里则是满负荷的消息。
emit messageReceived(link, message);//@@@@@@@@@@@@@
...

m_multiplexingEnabled//这个多路传输,看起来好像是消息广播,难道是用于多地面站???
}

此处将满载的消息发射,用于解码线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

// \src\comm\MAVLinkProtocol.h 信号定义
signals:
/** @brief Message received and directly copied via signal */
void messageReceived(LinkInterface* link, mavlink_message_t message);

// \src\Vehicle\Vehicle.cc 该槽函数 用于解码
connect(_mavlink, &MAVLinkProtocol::messageReceived, this, &Vehicle::_mavlinkMessageReceived);

// \src\ui\MAVLinkDecoder.cc 信号槽 用于显示???
MAVLinkDecoder::MAVLinkDecoder(MAVLinkProtocol* protocol, QObject *parent) :
QThread()
{
...
connect(protocol, &MAVLinkProtocol::messageReceived, this, &MAVLinkDecoder::receiveMessage);
}

此处先讲 MAVLinkDecoder::receiveMessage,解码作为单章。

1
2
3
4
5
6
7
8
9
10
11
12
//      \src\ui\MAVLinkDecoder.cc
void MAVLinkDecoder::receiveMessage(LinkInterface* link,mavlink_message_t message)
{
...
//调整消息中的时间,与地面站对齐。

// Send out all field values for this message
for (unsigned int i = 0; i < messageInfo[msgid].num_fields; ++i)
{
emitFieldValue(&message, i, time);//@@@@@@@@@@@@@@
}
}

此处将消息送到 emitFieldValue() 函数解码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
	//        \src\ui\MAVLinkDecoder.cc   源程序约300行,摘取部分注释;

void MAVLinkDecoder::emitFieldValue(mavlink_message_t* msg, int fieldid, quint64 time)
{
...
// 支持多组件检测,传感器的冗余检测;
componentMulti[msg->msgid] = true;
...
//此处根据 消息的数据类型 进行处理;
switch (messageInfo[msgid].fields[fieldid].type)
{
case MAVLINK_TYPE_CHAR:
if (messageInfo[msgid].fields[fieldid].array_length > 0) {
emit textMessageReceived(msg->sysid, msg->compid, MAV_SEVERITY_INFO, string);
} else {
emit valueChanged(msg->sysid, name, unit, b, time);
}
break;
case MAVLINK_TYPE_UINT8_T:
if (messageInfo[msgid].fields[fieldid].array_length > 0)
{
emit valueChanged(msg->sysid, QString("%1.%2").arg(name).arg(j), fieldType, nums[j], time);
}
else
{
emit valueChanged(msg->sysid, name, fieldType, u, time);
}
break;

....
....
....
case MAVLINK_TYPE_INT64_T:
if (messageInfo[msgid].fields[fieldid].array_length > 0)
{
emit valueChanged(msg->sysid, QString("%1.%2").arg(name).arg(j), fieldType, (qint64) nums[j], time);
}
else
{
emit valueChanged(msg->sysid, name, fieldType, (qint64) n, time);
}
break;
default:
qDebug() << "WARNING: UNKNOWN MAVLINK TYPE";
}
}

其中,主要用到了 两个信号:

1
2
3
signals:
void textMessageReceived(int uasid, int componentid, int severity, const QString& text);
void valueChanged(const int uasId, const QString& name, const QString& unit, const QVariant& value, const quint64 msec);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	//   \src\ui\uas\UASMessageView.cc   UASMessageViewWidget 构造函数中;
connect(_uasMessageHandler, &UASMessageHandler::textMessageReceived, this, &UASMessageViewWidget::handleTextMessage);

该槽函数用于(ui()->plainTextEdit)消息显示,支持滚动条;

####################################################################

// \src\ui\MainWindow.cc _buildCommonWidgets(void) 在构造函数中调用;
connect(mavlinkDecoder.data(), &MAVLinkDecoder::valueChanged, this, &MainWindow::valueChanged);//信号转发,没有找到相关的槽

// \src\ui\linechart\Linecharts.cc
connect(_mavlinkDecoder, &MAVLinkDecoder::valueChanged, widget, &LinechartWidget::appendData);
//用于显示,下同。

// \src\ui\uas\UASQuickView.cc
connect(decoder,SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)),this,SLOT(valueChanged(int,QString,QString,QVariant,quint64)));

MAVLink 解码

MAVLink 解码分为两个部分:

  1. 一部分在 Vehicle 类中,这里解码后,主要对地面站的飞行器状态进行更新;
  2. 另一部分在 UAS 类中,这里有完整的解码函数,解码得到对应的信息;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void Vehicle::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t message)
{
...
//此处通过 switch 函数调取相应的消息处理函数;
//此处只对几个重要消息进行解码,并更新地面站对应的状态;与后面的解码不冲突;
switch (message.msgid) {
case MAVLINK_MSG_ID_HOME_POSITION:
_handleHomePosition(message);
break;
case MAVLINK_MSG_ID_HEARTBEAT:
_handleHeartbeat(message);
break;
...
...
case MAVLINK_MSG_ID_AUTOPILOT_VERSION:
_handleAutopilotVersion(message);
break;
case MAVLINK_MSG_ID_WIND_COV:
_handleWindCov(message);
break;

// Following are ArduPilot dialect messages

case MAVLINK_MSG_ID_WIND:
_handleWind(message);
break;
}

emit mavlinkMessageReceived(message);//发送至文件管理,文件传输??

_uas->receiveMessage(message);//这里是解码函数;
}

Vehicle 类的解码

1
2
3
4
5
6
7
8
9
10
11
12
void _handleHomePosition(mavlink_message_t& message);
void _handleHeartbeat(mavlink_message_t& message);
void _handleRCChannels(mavlink_message_t& message);
void _handleRCChannelsRaw(mavlink_message_t& message);
void _handleBatteryStatus(mavlink_message_t& message);
void _handleSysStatus(mavlink_message_t& message);
void _handleWindCov(mavlink_message_t& message);
void _handleWind(mavlink_message_t& message);
void _handleVibration(mavlink_message_t& message);
void _handleExtendedSysState(mavlink_message_t& message);
void _handleCommandAck(mavlink_message_t& message);
void _handleAutopilotVersion(mavlink_message_t& message);

这里只对 心跳包 详细描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void Vehicle::_handleHeartbeat(mavlink_message_t& message)
{
_connectionActive();

mavlink_heartbeat_t heartbeat;

mavlink_msg_heartbeat_decode(&message, &heartbeat);//Mavlink 解码;

bool newArmed = heartbeat.base_mode & MAV_MODE_FLAG_DECODE_POSITION_SAFETY;

if (_armed != newArmed) {
_armed = newArmed;
emit armedChanged(_armed);

// We are transitioning to the armed state, begin tracking trajectory points for the map
if (_armed) {
_mapTrajectoryStart();//解锁后,启动轨迹追踪;
} else {
_mapTrajectoryStop();
}
}

if (heartbeat.base_mode != _base_mode || heartbeat.custom_mode != _custom_mode) {
_base_mode = heartbeat.base_mode;
_custom_mode = heartbeat.custom_mode;
emit flightModeChanged(flightMode());//间接发送到 QML 用于显示;
}
}

UAS 类的解码

此处是地面站中完整的解码程序;
内容较为简单,不做注释;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
void UAS::receiveMessage(mavlink_message_t message)
{
//对于冗余的传感器,
//Prefer IMU 2 over IMU 1

...
...

switch (message.msgid)
{
case MAVLINK_MSG_ID_HEARTBEAT:
{
if (multiComponentSourceDetected && wrongComponent)
{
break;
}
mavlink_heartbeat_t state;
mavlink_msg_heartbeat_decode(&message, &state);

// Send the base_mode and system_status values to the plotter. This uses the ground time
// so the Ground Time checkbox must be ticked for these values to display
quint64 time = getUnixTime();
QString name = QString("M%1:HEARTBEAT.%2").arg(message.sysid);
emit valueChanged(uasId, name.arg("base_mode"), "bits", state.base_mode, time);
emit valueChanged(uasId, name.arg("custom_mode"), "bits", state.custom_mode, time);
emit valueChanged(uasId, name.arg("system_status"), "-", state.system_status, time);

if ((state.system_status != this->status) && state.system_status != MAV_STATE_UNINIT)
{
this->status = state.system_status;
getStatusForCode((int)state.system_status, uasState, stateDescription);
emit statusChanged(this, uasState, stateDescription);
emit statusChanged(this->status);
}

// We got the mode
receivedMode = true;
}

break;

case MAVLINK_MSG_ID_SYS_STATUS:
...
...
...//此处省略约400行;
...
case MAVLINK_MSG_ID_LOG_ENTRY:
{
mavlink_log_entry_t log;
mavlink_msg_log_entry_decode(&message, &log);
emit logEntry(this, log.time_utc, log.size, log.id, log.num_logs, log.last_log_num);
}
break;

case MAVLINK_MSG_ID_LOG_DATA:
{
mavlink_log_data_t log;
mavlink_msg_log_data_decode(&message, &log);
emit logData(this, log.ofs, log.id, log.count, log.data);
}
break;

default:
break;
}


}

[toc]

QGroundControl笔记 —— qmldir 模块管理

参考:http://blog.csdn.net/qyvlik/article/details/44758509、http://blog.csdn.net/king523103/article/details/45198589

qmldir文件,用于qml模块化管理。

这里的模块化包括

  • c++扩展的模块
  • 纯qml拓展的模块

本章以 \src\FlightMap\qmldir 为例,讲解 qmldir 的使用方法。

首先是模块的调用:

1
2
//\src\FlightDisplay\FlightDisplayView.qml
import QGroundControl.FlightMap 1.0

qmldir 文件需要在生成可执行文件时,拷贝到程序相同目录中,在生成文件中未找到对应 FlightMap 的 qmldir 文件,疑似通过资源文件整合。

qmldir 文件源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Module QGroundControl.FlightMap
# module 后面的那个单词就是模块名,一般与qmldir文件所处文件夹同名


# Main view controls
FlightMap 1.0 FlightMap.qml
QGCVideoBackground 1.0 QGCVideoBackground.qml
# 说明一个自定义控件的名字,再指定版本号,最后指定对应的qml文件

# Widgets
InstrumentSwipeView 1.0 InstrumentSwipeView.qml
MapScale 1.0 MapScale.qml
QGCArtificialHorizon 1.0 QGCArtificialHorizon.qml
QGCAttitudeHUD 1.0 QGCAttitudeHUD.qml
QGCAttitudeWidget 1.0 QGCAttitudeWidget.qml
QGCCompassWidget 1.0 QGCCompassWidget.qml
QGCInstrumentWidget 1.0 QGCInstrumentWidget.qml
QGCInstrumentWidgetAlternate 1.0 QGCInstrumentWidgetAlternate.qml
QGCPitchIndicator 1.0 QGCPitchIndicator.qml
QGCSlider 1.0 QGCSlider.qml
ValuesWidget 1.0 ValuesWidget.qml
VibrationWidget 1.0 VibrationWidget.qml

# Map items
MissionItemIndicator 1.0 MissionItemIndicator.qml
MissionItemView 1.0 MissionItemView.qml
MissionLineView 1.0 MissionLineView.qml
VehicleMapItem 1.0 VehicleMapItem.qml

同时支持一下信息添加:

  • 导入描述信息

    `typeinfo mymodule.qmltypes`
    
  • 依赖文件

    `depend MyOtherModule 1.0`
    

更多信息请访问,Qt help : Module Definition qmldir Files