QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
LinkInterface.cc
Go to the documentation of this file.
1#include "LinkInterface.h"
2#include "MAVLinkLib.h"
3#include "LinkManager.h"
4#include "AppMessages.h"
5#include "QGCApplication.h"
7#include "SigningController.h"
8
9#include <QtCore/QThread>
10#include <QtQml/QQmlEngine>
11
12QGC_LOGGING_CATEGORY(LinkInterfaceLog, "Comms.LinkInterface")
13
14namespace {
15 constexpr int WORKER_SHUTDOWN_TIMEOUT_MS = 3000;
16}
17
19 : QObject(parent)
20 , _config(config)
21{
22 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
23}
24
26{
27 if (_vehicleReferenceCount != 0) {
28 qCWarning(LinkInterfaceLog) << "still have vehicle references:" << _vehicleReferenceCount;
29 }
30
31 _config.reset();
32}
33
34void LinkInterface::_shutdownWorkerThread(QThread *thread, const QLoggingCategory &category, bool allowTerminate)
35{
36 if (!thread) {
37 return;
38 }
39
40 // Self-shutdown: wait() would fail and terminate() would kill the calling thread mid-destructor
41 if (thread->isCurrentThread()) {
42 qCCritical(category) << "Shutdown called from worker thread itself, orphaning";
43 thread->quit();
44 _orphanWorkerThread(thread);
45 return;
46 }
47
48 thread->quit();
49 if (thread->wait(WORKER_SHUTDOWN_TIMEOUT_MS)) {
50 return;
51 }
52
53 if (allowTerminate) {
54 qCWarning(category) << "Worker thread did not stop within timeout, terminating";
55 thread->terminate();
56 if (thread->wait(WORKER_SHUTDOWN_TIMEOUT_MS)) {
57 return;
58 }
59 }
60
61 // Orphan the running thread — leaking it beats the ~QThread abort in deleteChildren()
62 qCCritical(category) << "Worker thread could not be stopped, leaking it to avoid abort";
63 _orphanWorkerThread(thread);
64}
65
66void LinkInterface::_orphanWorkerThread(QThread *thread)
67{
68 thread->setParent(nullptr);
69
70 // Workers hold raw pointers into the configuration: keep it alive until the thread
71 // finishes so a stalled worker that resumes can't dereference a destroyed config.
72 (void) QObject::connect(thread, &QThread::finished, thread, [config = _config]() { Q_UNUSED(config); });
73
74 // Self-heal if the thread eventually exits: deleting the thread destroys the
75 // connection above, releasing the config. At app exit there is no main loop to
76 // deliver this, so both leak — the intended last-resort behavior.
77 (void) QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
78}
79
81{
82 if (!mavlinkChannelIsSet()) {
83 qCWarning(LinkInterfaceLog) << "mavlinkChannelIsSet() == false";
84 }
85
86 return _mavlinkChannel;
87}
88
90{
91 return (LinkManager::invalidMavlinkChannel() != _mavlinkChannel);
92}
93
95{
96 Q_ASSERT(!mavlinkChannelIsSet());
97
98 if (mavlinkChannelIsSet()) {
99 qCWarning(LinkInterfaceLog) << "already have" << _mavlinkChannel;
100 return true;
101 }
102
103 _mavlinkChannel = LinkManager::instance()->allocateMavlinkChannel();
104
105 if (!mavlinkChannelIsSet()) {
106 qCWarning(LinkInterfaceLog) << "failed";
107 return false;
108 }
109
110 qCDebug(LinkInterfaceLog) << "_allocateMavlinkChannel" << _mavlinkChannel;
111
112 mavlink_set_proto_version(_mavlinkChannel, MAVLINK_VERSION); // We only support v2 protcol
113
114 _signingController = std::make_unique<SigningController>(static_cast<mavlink_channel_t>(_mavlinkChannel));
115 _signingController->clearSigning();
116
117 qCDebug(LinkInterfaceLog) << "SigningController created for channel" << _mavlinkChannel
118 << (isSecureConnection() ? "(secure)" : "(will auto-detect)");
119
120 return true;
121}
122
124{
125 qCDebug(LinkInterfaceLog) << _mavlinkChannel;
126
127 if (!mavlinkChannelIsSet()) {
128 return;
129 }
130
131 // Destroy the controller before freeing the channel so it can flush the final timestamp.
132 _signingController.reset();
133
134 // mavlink_reset_channel_status only resets parse_state — null signing/streams explicitly to avoid dangling derefs.
135 mavlink_status_t* const status = mavlink_get_channel_status(_mavlinkChannel);
136 status->signing = nullptr;
137 status->signing_streams = nullptr;
138 mavlink_reset_channel_status(_mavlinkChannel);
139
140 LinkManager::instance()->freeMavlinkChannel(_mavlinkChannel);
141 _mavlinkChannel = LinkManager::invalidMavlinkChannel();
142}
143
144void LinkInterface::writeBytesThreadSafe(const char *bytes, int length)
145{
146 const QByteArray data(bytes, length);
147 (void) QMetaObject::invokeMethod(this, "_writeBytes", Qt::AutoConnection, data);
148}
149
151{
152 // Re-sign with a current timestamp; the cached-resend path (Vehicle::sendMessageMultiple) otherwise ships frozen
153 // signed bytes whose timestamp drifts behind wall clock and gets OLD_TIMESTAMP-rejected. No-op when signing is
154 // disabled or the message isn't outgoing-signed. The secret key stays in the signing layer.
155 if (_signingController) {
156 (void) _signingController->signOutgoing(message);
157 }
158
159 uint8_t buffer[MAVLINK_MAX_PACKET_LEN];
160 const int len = mavlink_msg_to_send_buffer(buffer, &message);
161 writeBytesThreadSafe(reinterpret_cast<const char *>(buffer), len);
162}
163
165{
166 if (_vehicleReferenceCount != 0) {
167 _vehicleReferenceCount--;
169 } else {
170 qCWarning(LinkInterfaceLog) << "called with no vehicle references";
171 }
172}
173
175{
176 if (_vehicleReferenceCount == 0) {
177 // Since there are no vehicles on the link we can disconnect it right now
178 disconnect();
179 } else {
180 // If there are still vehicles on this link we allow communication lost to trigger and don't automatically disconect until all the vehicles go away
181 }
182}
183
185{
186 if (!_radioStatusSeen) {
187 _radioStatusSeen = true;
188 qCDebug(LinkInterfaceLog) << "RADIO_STATUS seen, treating link as radio link:" << _config->name();
189 }
190}
191
193{
194 if (_mavlinkV1TrafficReported || _mavlinkV2TrafficSeen) {
195 return;
196 }
197
198 // Defer the warning: ArduPilot starts out sending v1 and upgrades to v2 once it sees v2
199 // traffic from QGC. Only warn if the link never produces v2 within the grace period.
200 if (!_mavlinkV1FirstSeenTimer.isValid()) {
201 _mavlinkV1FirstSeenTimer.start();
202 }
203 if (_mavlinkV1FirstSeenTimer.elapsed() < _mavlinkV1TrafficGraceMsecs) {
204 return;
205 }
206
207 _mavlinkV1TrafficReported = true;
208
210 const QString linkName = linkConfig ? linkConfig->name() : QStringLiteral("unknown");
211 qCWarning(LinkInterfaceLog) << "MAVLink v1 traffic detected on link" << linkName;
212 const QString message = tr("MAVLink v1 traffic detected on link '%1'. "
213 "%2 only supports MAVLink v2. "
214 "Please ensure your vehicle is configured to use MAVLink v2.")
215 .arg(linkName).arg(qgcApp()->applicationName());
216 QGC::showAppMessage(message);
217}
Config config
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
mavlink_channel_t
mavlink_status_t * mavlink_get_channel_status(uint8_t chan)
Definition QGCMAVLink.cc:53
#define qgcApp()
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
virtual ~LinkInterface()
uint8_t mavlinkChannel() const
void _shutdownWorkerThread(QThread *thread, const QLoggingCategory &category, bool allowTerminate=true)
virtual void _freeMavlinkChannel()
LinkInterface(SharedLinkConfigurationPtr &config, QObject *parent=nullptr)
Links are only created by LinkManager so constructor is not public.
virtual Q_INVOKABLE void disconnect()=0
void sendMessageThreadSafe(mavlink_message_t &message)
void reportMavlinkV1Traffic()
void _connectionRemoved()
bool mavlinkChannelIsSet() const
virtual bool _allocateMavlinkChannel()
void removeVehicleReference()
virtual bool isSecureConnection() const
void writeBytesThreadSafe(const char *bytes, int length)
SharedLinkConfigurationPtr linkConfiguration()
SharedLinkConfigurationPtr _config
void reportRadioStatusReceived()
uint8_t allocateMavlinkChannel()
static LinkManager * instance()
void freeMavlinkChannel(uint8_t channel)
static constexpr uint8_t invalidMavlinkChannel()
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.
Definition AppMessages.cc:9