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