QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
RemoteIDManager.cc
Go to the documentation of this file.
1#include "RemoteIDManager.h"
2#include "MAVLinkLib.h"
3#include "SettingsManager.h"
4#include "RemoteIDSettings.h"
5#include "PositionManager.h"
6#include "Vehicle.h"
8#include "MAVLinkProtocol.h"
10
11QGC_LOGGING_CATEGORY(RemoteIDManagerLog, "Vehicle.RemoteIDManager")
12
13#define AREA_COUNT 1
14#define AREA_RADIUS 0
15#define MAVLINK_UNKNOWN_METERS -1000.0f
16#define MAVLINK_UNKNOWN_LAT 0
17#define MAVLINK_UNKNOWN_LON 0
18#define SENDING_RATE_MSEC 1000
19#define ALLOWED_GPS_DELAY 5000
20#define RID_TIMEOUT 2500 // Messages should be arriving at 1 Hz, so we set a 2 second timeout
21
22const uint8_t* RemoteIDManager::_id_or_mac_unknown = new uint8_t[MAVLINK_MSG_OPEN_DRONE_ID_OPERATOR_ID_FIELD_ID_OR_MAC_LEN]();
23
25 : QObject (vehicle)
26 , _vehicle (vehicle)
27 , _settings (nullptr)
28 , _armStatusGoodToArm (false)
29 , _ridDeviceCommsGood (false)
30 , _gcsPositionUsable (false)
31 , _vehicleReportsBasicIDMissing(false)
32 , _emergencyDeclared (false)
33 , _targetSystem (0) // By default 0 means broadcast
34 , _targetComponent (0) // By default 0 means broadcast
35 , _enforceSendingSelfID (false)
36{
38
39 // Timer to track a healthy RID device. When expired we let the operator know
40 _odidTimeoutTimer.setSingleShot(true);
41 _odidTimeoutTimer.setInterval(RID_TIMEOUT);
42 connect(&_odidTimeoutTimer, &QTimer::timeout, this, &RemoteIDManager::_odidTimeout);
43
44 // Timer to send messages at a constant rate
45 _sendMessagesTimer.setInterval(SENDING_RATE_MSEC);
46 connect(&_sendMessagesTimer, &QTimer::timeout, this, &RemoteIDManager::_sendMessages);
47
48 // Assign vehicle sysid and compid. GCS must target these messages to autopilot, and autopilot will redirect them to RID device
49 _targetSystem = _vehicle->id();
50 _targetComponent = _vehicle->compId();
51}
52
54{
55 switch (message.msgid) {
56 // So far we are only listening to this one, as heartbeat won't be sent if connected by CAN
57 case MAVLINK_MSG_ID_OPEN_DRONE_ID_ARM_STATUS:
58 _handleArmStatus(message);
59 default:
60 break;
61 }
62}
63
64// This slot will be called if we stop receiving heartbeats for more than RID_TIMEOUT seconds
65void RemoteIDManager::_odidTimeout()
66{
67 _ridDeviceCommsGood = false;
68 _sendMessagesTimer.stop(); // We stop sending messages if the communication with the RID device is down
70 qCDebug(RemoteIDManagerLog) << "We stopped receiving heartbeat from RID device.";
71}
72
73// Parsing of the ARM_STATUS message comming from the RID device
74void RemoteIDManager::_handleArmStatus(mavlink_message_t& message)
75{
76 // Compid must be ODID_TXRX_X
77 if ( (message.compid < MAV_COMP_ID_ODID_TXRX_1) || (message.compid > MAV_COMP_ID_ODID_TXRX_3) ) {
78 // or same as autopilot, in the case of Ardupilot and CAN RID modules
79 if (message.compid != MAV_COMP_ID_AUTOPILOT1) {
80 return;
81 }
82 }
83
84 // Sanity check, only get messages from same sysid
85 if (_vehicle->id() != message.sysid) {
86 return;
87 }
88
89 if (!_available) {
90 _available = true;
91 emit availableChanged();
92 qCDebug(RemoteIDManagerLog) << "Receiving ODID_ARM_STATUS for first time. Mavlink Open Drone ID support is available.";
93 }
94
95 // We set the targetsystem
96 if (_targetSystem != message.sysid) {
97 _targetSystem = message.sysid;
98 qCDebug(RemoteIDManagerLog) << "Subscribing to ODID messages coming from system " << _targetSystem;
99 }
100
101 if (!_ridDeviceCommsGood) {
102 _ridDeviceCommsGood = true;
103 _sendMessagesTimer.start(); // Start sending our messages
105 qCDebug(RemoteIDManagerLog) << "Receiving ODID_ARM_STATUS from RID device";
106 }
107
108 // Restart the timeout
109 _odidTimeoutTimer.start();
110
111 // CompId and sysId are correct, we can proceed
112 mavlink_open_drone_id_arm_status_t armStatus;
113 mavlink_msg_open_drone_id_arm_status_decode(&message, &armStatus);
114
115 if (armStatus.status == MAV_ODID_ARM_STATUS_GOOD_TO_ARM) {
116 // If good to arm, even if basic ID is not set on GCS, it was set by remoteID parameters, so GCS one would be optional in this case
117 if (_vehicleReportsBasicIDMissing) {
118 _vehicleReportsBasicIDMissing = false;
120 }
121 // Clear any stale error text so the UI stops showing it once the device recovers
122 if (!_armStatusError.isEmpty()) {
123 _armStatusError.clear();
125 }
126 if (!_armStatusGoodToArm) {
127 _armStatusGoodToArm = true;
129 qCDebug(RemoteIDManagerLog) << "Arm status GOOD TO ARM.";
130 }
131 }
132
133 if (armStatus.status == MAV_ODID_ARM_STATUS_PRE_ARM_FAIL_GENERIC) {
134 if (_armStatusGoodToArm) {
135 _armStatusGoodToArm = false;
137 }
138 // MAVLink char fields are only null-terminated when shorter than the field, so bound the decode
139 const QString armStatusError = QString::fromUtf8(armStatus.error, qstrnlen(armStatus.error, sizeof(armStatus.error)));
140 // The flag tracks the current reported error: set while the device blames basic ID,
141 // cleared when it fails for a different reason (don't leave the UI blaming basic ID)
142 const bool basicIDMissing = (armStatusError == QStringLiteral("missing basic_id message"));
143 if (_vehicleReportsBasicIDMissing != basicIDMissing) {
144 _vehicleReportsBasicIDMissing = basicIDMissing;
145 if (basicIDMissing) {
146 qCDebug(RemoteIDManagerLog) << "Arm status error, basic_id is not set in RID device nor in GCS!";
147 }
149 }
150 if (_armStatusError != armStatusError) {
151 _armStatusError = armStatusError;
153 qCDebug(RemoteIDManagerLog) << "Arm status error:" << _armStatusError;
154 }
155 }
156}
157
158// Function that sends messages periodically
159void RemoteIDManager::_sendMessages()
160{
161 // We always try to send System
162 _sendSystem();
163
164 // only send it if the information is correct and the tickbox in settings is set
165 if (_settings->basicIDValid() && _settings->sendBasicID()->rawValue().toBool()) {
166 _sendBasicID();
167 }
168
169 // We only send selfID if the pilot wants it or in case of a declared emergency. If an emergency is cleared
170 // we also keep sending the message, to be sure the non emergency state makes it up to the vehicle
171 if (_settings->sendSelfID()->rawValue().toBool() || _emergencyDeclared || _enforceSendingSelfID) {
172 _sendSelfIDMsg();
173 }
174
175 // We only send the OperatorID if the pilot wants it or if the region we have set is europe.
176 // To be able to send it, it needs to be filled correclty
177 if ((_settings->sendOperatorID()->rawValue().toBool() || (_settings->region()->rawValue().toInt() == static_cast<int>(RemoteIDSettings::RegionOperation::EU))) && _settings->operatorIDValidForRegion()) {
178 _sendOperatorID();
179 }
180
181}
182
183void RemoteIDManager::_sendSelfIDMsg()
184{
185 WeakLinkInterfacePtr weakLink = _vehicle->vehicleLinkManager()->primaryLink();
186 SharedLinkInterfacePtr sharedLink = weakLink.lock();
187
188 if (sharedLink) {
190 const QByteArray selfIdDescription = _getSelfIDDescription();
191
192 mavlink_msg_open_drone_id_self_id_pack_chan(MAVLinkProtocol::instance()->getSystemId(),
194 sharedLink->mavlinkChannel(),
195 &msg,
196 _targetSystem,
197 _targetComponent,
198 _id_or_mac_unknown,
199 _emergencyDeclared ? 1 : _settings->selfIDType()->rawValue().toInt(), // If emergency is delcared we send directly a 1 (1 = EMERGENCY)
200 selfIdDescription.constData()); // Depending on the type of SelfID we send a different description
201 _vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
202 }
203}
204
205// We need to return the correct description for the self ID type we have selected
206QByteArray RemoteIDManager::_getSelfIDDescription() const
207{
208 QString descriptionToSend;
209
210 if (_emergencyDeclared) {
211 // If emergency is declared we dont care about the settings and we send emergency directly
212 descriptionToSend = _settings->selfIDEmergency()->rawValue().toString();
213 } else {
214 switch (_settings->selfIDType()->rawValue().toInt()) {
215 case 0:
216 descriptionToSend = _settings->selfIDFree()->rawValue().toString();
217 break;
218 case 1:
219 descriptionToSend = _settings->selfIDEmergency()->rawValue().toString();
220 break;
221 case 2:
222 descriptionToSend = _settings->selfIDExtended()->rawValue().toString();
223 break;
224 default:
225 descriptionToSend = _settings->selfIDEmergency()->rawValue().toString();
226 }
227 }
228
229 QByteArray descriptionBuffer = descriptionToSend.toLocal8Bit();
230 descriptionBuffer.resize(MAVLINK_MSG_OPEN_DRONE_ID_SELF_ID_FIELD_DESCRIPTION_LEN, '\0');
231 return descriptionBuffer;
232}
233
234void RemoteIDManager::_sendOperatorID()
235{
236 WeakLinkInterfacePtr weakLink = _vehicle->vehicleLinkManager()->primaryLink();
237 SharedLinkInterfacePtr sharedLink = weakLink.lock();
238
239 if (sharedLink) {
241
242 // Each region stores its own operator ID; broadcast the one matching the selected region
243 const bool isEURegion = (_settings->region()->rawValue().toInt() == static_cast<int>(RemoteIDSettings::RegionOperation::EU));
244 Fact* const operatorIDFact = isEURegion ? _settings->operatorIDEU() : _settings->operatorIDFAA();
245 QByteArray bytesOperatorID = operatorIDFact->rawValue().toString().toLocal8Bit();
246 bytesOperatorID.resize(MAVLINK_MSG_OPEN_DRONE_ID_OPERATOR_ID_FIELD_OPERATOR_ID_LEN, '\0');
247
248 mavlink_msg_open_drone_id_operator_id_pack_chan(
249 MAVLinkProtocol::instance()->getSystemId(),
251 sharedLink->mavlinkChannel(),
252 &msg,
253 _targetSystem,
254 _targetComponent,
255 _id_or_mac_unknown,
256 _settings->operatorIDType()->rawValue().toInt(),
257 bytesOperatorID.constData());
258
259 _vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
260 }
261}
262
263void RemoteIDManager::_updateGcsPositionStatus(bool usable, const QString& error)
264{
265 if (!error.isEmpty() && _gcsPositionError != error) {
266 _gcsPositionError = error;
267 qCWarning(RemoteIDManagerLog) << "GCS GPS error:" << error;
268 }
269 if (_gcsPositionUsable != usable) {
270 _gcsPositionUsable = usable;
271 if (usable) {
272 _gcsPositionError.clear();
273 }
275 }
276}
277
278void RemoteIDManager::_sendSystem()
279{
280 QGeoCoordinate gcsPosition(0, 0, 0);
281 const uint32_t locationType = _settings->locationType()->rawValue().toUInt();
282 // Location types:
283 // 0 -> TAKEOFF (not supported yet)
284 // 1 -> LIVE GNNS
285 // 2 -> FIXED
286 if (locationType == LocationTypes::FIXED) {
287 const double lat = _settings->latitudeFixed()->rawValue().toDouble();
288 const double lon = _settings->longitudeFixed()->rawValue().toDouble();
289 const double alt = _settings->altitudeFixed()->rawValue().toDouble();
290
291 // For FIXED location, we first check that the values are valid. Then we populate our position
292 if (lat >= -90.0 && lat <= 90.0 && lon >= -180.0 && lon <= 180.0) {
293 gcsPosition = QGeoCoordinate(lat, lon, alt);
294 _updateGcsPositionStatus(true);
295 } else {
296 _updateGcsPositionStatus(false, "The provided coordinates for FIXED position are invalid.");
297 }
298 } else {
300 QGeoPositionInfo geoPositionInfo = positionManager->geoPositionInfo();
301 gcsPosition = positionManager->gcsPosition();
302 const QDateTime gcsPositionTimestamp = positionManager->gcsPositionTimestamp();
303
304 // gcsPosition only carries an altitude when the fix's vertical accuracy is within the
305 // strict gate QGCPositionManager applies for consumers which act on it, such as Follow Me
306 // and update-home-position. Remote ID mandates an operator altitude in FAA regions and
307 // OPEN_DRONE_ID_SYSTEM has no accuracy field for it, so a loosely known altitude is better
308 // than none here: take it straight from the fix whenever the fix reports one.
309 const QGeoCoordinate fixCoordinate = geoPositionInfo.coordinate();
310 if (fixCoordinate.type() == QGeoCoordinate::Coordinate3D) {
311 gcsPosition.setAltitude(fixCoordinate.altitude());
312 }
313
314 if (!geoPositionInfo.isValid()) {
315 // Only warn if we've previously received a valid fix; otherwise the source is
316 // still initializing and the absence of data is expected, not an error.
317 _updateGcsPositionStatus(false, gcsPositionTimestamp.isValid()
318 ? QStringLiteral("GCS GPS data is not valid.")
319 : QString());
320 } else if (positionManager->gcsPositioningError() != QGeoPositionInfoSource::NoError && positionManager->gcsPositioningError() != QGeoPositionInfoSource::UpdateTimeoutError) {
321 _updateGcsPositionStatus(false, QString("GCS GPS data error: %1").arg(positionManager->gcsPositioningError()));
322 } else if (!gcsPosition.isValid() || gcsPosition.type() == QGeoCoordinate::InvalidCoordinate) {
323 _updateGcsPositionStatus(false, "GCS GPS data error: Invalid coordinate type.");
324 } else if (_settings->region()->rawValue().toInt() == static_cast<int>(RemoteIDSettings::RegionOperation::FAA) && gcsPosition.type() != QGeoCoordinate::Coordinate3D) {
325 // FAA requires altitude data, or else the GPS data is not good
326 _updateGcsPositionStatus(false, "GCS GPS data error: Altitude data is mandatory for FAA regions.");
327 } else if (!gcsPositionTimestamp.isValid() || (gcsPositionTimestamp.msecsTo(QDateTime::currentDateTimeUtc()) > ALLOWED_GPS_DELAY)) {
328 _updateGcsPositionStatus(false, "GCS GPS data is older than 5 seconds");
329 } else {
330 _updateGcsPositionStatus(true);
331 }
332 }
333
334 WeakLinkInterfacePtr weakLink = _vehicle->vehicleLinkManager()->primaryLink();
335 SharedLinkInterfacePtr sharedLink = weakLink.lock();
336
337 if (sharedLink) {
339
340 mavlink_msg_open_drone_id_system_pack_chan(MAVLinkProtocol::instance()->getSystemId(),
342 sharedLink->mavlinkChannel(),
343 &msg,
344 _targetSystem,
345 _targetComponent,
346 _id_or_mac_unknown,
347 _settings->locationType()->rawValue().toUInt(),
348 _settings->classificationType()->rawValue().toUInt(),
349 _gcsPositionUsable ? ( gcsPosition.latitude() * 1.0e7 ) : MAVLINK_UNKNOWN_LAT,
350 _gcsPositionUsable ? ( gcsPosition.longitude() * 1.0e7 ) : MAVLINK_UNKNOWN_LON,
355 _settings->categoryEU()->rawValue().toUInt(),
356 _settings->classEU()->rawValue().toUInt(),
357 _gcsPositionUsable ? gcsPosition.altitude() : MAVLINK_UNKNOWN_METERS,
358 _timestamp2019()), // Time stamp needs to be since 00:00:00 1/1/2019
359 _vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
360 }
361}
362
363// Returns seconds elapsed since 00:00:00 1/1/2019
364uint32_t RemoteIDManager::_timestamp2019()
365{
366 uint32_t secsSinceEpoch2019 = 1546300800; // Secs elapsed since epoch to 1-1-2019
367
368 return ((QDateTime::currentDateTime().currentSecsSinceEpoch()) - secsSinceEpoch2019);
369}
370
371void RemoteIDManager::_sendBasicID()
372{
373 WeakLinkInterfacePtr weakLink = _vehicle->vehicleLinkManager()->primaryLink();
374 SharedLinkInterfacePtr sharedLink = weakLink.lock();
375
376 if (sharedLink) {
378
379 QString basicIDTemp = _settings->basicID()->rawValue().toString();
380 QByteArray ba = basicIDTemp.toLocal8Bit();
381 // To make sure the buffer is large enough to fit the message. It will add padding bytes if smaller, or exclude the extra ones if bigger
382 ba.resize(MAVLINK_MSG_OPEN_DRONE_ID_BASIC_ID_FIELD_UAS_ID_LEN, '\0');
383
384 mavlink_msg_open_drone_id_basic_id_pack_chan(MAVLinkProtocol::instance()->getSystemId(),
386 sharedLink->mavlinkChannel(),
387 &msg,
388 _targetSystem,
389 _targetComponent,
390 _id_or_mac_unknown,
391 _settings->basicIDType()->rawValue().toUInt(),
392 _settings->basicIDUaType()->rawValue().toUInt(),
393 reinterpret_cast<const unsigned char*>(ba.constData())),
394
395 _vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
396 }
397}
398
400{
401 _emergencyDeclared = declare;
403 // Wether we are starting an emergency or cancelling it, we need to enforce sending
404 // this message. Otherwise, if non optimal connection quality, vehicle RID device
405 // could remain in the wrong state. It is clarified to the user in remoteidsettings.qml
406 _enforceSendingSelfID = true;
407
408 qCDebug(RemoteIDManagerLog) << ( declare ? "Emergency declared." : "Emergency cleared.");
409}
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
std::weak_ptr< LinkInterface > WeakLinkInterfacePtr
Error error
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
#define MAVLINK_UNKNOWN_LAT
#define SENDING_RATE_MSEC
#define AREA_COUNT
#define MAVLINK_UNKNOWN_METERS
#define ALLOWED_GPS_DELAY
#define AREA_RADIUS
#define RID_TIMEOUT
#define MAVLINK_UNKNOWN_LON
A Fact is used to hold a single value within the system.
Definition Fact.h:17
QVariant rawValue() const
Definition Fact.h:90
static int getComponentId()
static MAVLinkProtocol * instance()
QGeoPositionInfo geoPositionInfo() const
static QGCPositionManager * instance()
QDateTime gcsPositionTimestamp() const
QGeoCoordinate gcsPosition() const
QGeoPositionInfoSource::Error gcsPositioningError() const
void availableChanged()
void mavlinkMessageReceived(mavlink_message_t &message)
void vehicleReportsBasicIDMissingChanged()
Q_INVOKABLE void setEmergency(bool declare)
void armStatusErrorChanged()
QString armStatusError(void) const
void ridDeviceCommsGoodChanged()
void armStatusGoodToArmChanged()
void emergencyDeclaredChanged()
RemoteIDManager(Vehicle *vehicle)
true: RID device reports MAV_ODID_ARM_STATUS_GOOD_TO_ARM
void gcsPositionUsableChanged()
bool operatorIDValidForRegion() const
bool basicIDValid() const
true: the basic ID entered in settings is complete enough to broadcast
static SettingsManager * instance()
RemoteIDSettings * remoteIDSettings() const
WeakLinkInterfacePtr primaryLink() const
VehicleLinkManager * vehicleLinkManager()
Definition Vehicle.h:579
int id() const
Definition Vehicle.h:429
bool sendMessageOnLinkThreadSafe(LinkInterface *link, mavlink_message_t message)
Definition Vehicle.cc:1386
int compId() const
Definition Vehicle.h:430