5#include <QtCore/QCoreApplication>
6#include <QtCore/QPermissions>
7#include <QtCore/QSignalBlocker>
19#if defined(Q_OS_ANDROID)
22#elif defined(Q_OS_DARWIN)
35 , _recoveryTask(_scheduler, this)
38 qCDebug(PositionManagerLog) <<
this;
44 qCDebug(PositionManagerLog) <<
"Position manager shutdown:" <<
this;
49 _receiver->disconnect(
this);
61 _receiverUsableSinceMs.reset();
70 _platformSourceFactory = std::move(factory);
83 if (std::exchange(_shutdown,
true)) {
86 qCDebug(PositionManagerLog) <<
"Releasing position sources";
90void PositionManager::_setupPositionSources()
92 auto* platformSource = _platformSourceFactory ? _platformSourceFactory(
this) : nullptr;
93 const bool custom = platformSource !=
nullptr;
96 platformSource = QGeoPositionInfoSource::createDefaultSource({{QStringLiteral(
"useMslAltitude"),
false}},
this);
98 qCDebug(PositionManagerLog) <<
"Device position backend:" << platformSource->sourceName();
106void PositionManager::_handlePermissionStatus(Qt::PermissionStatus permissionStatus)
111 if (permissionStatus == Qt::PermissionStatus::Granted) {
112 _setupPositionSources();
118void PositionManager::_checkPermission()
120 QLocationPermission locationPermission;
121 locationPermission.setAccuracy(QLocationPermission::Precise);
122 const auto status = QCoreApplication::instance()->checkPermission(locationPermission);
123 if (status == Qt::PermissionStatus::Undetermined) {
125 QCoreApplication::instance()->requestPermission(
126 locationPermission,
this,
127 [
this](
const QPermission& permission) { _handlePermissionStatus(permission.status()); });
129 _handlePermissionStatus(status);
136 _usingPluginSource = custom;
137 _deviceAltitudeDatum = altitudeDatum;
138 _platformStatus = status;
139 if (source == _device) {
146 connect(source, &QGeoPositionInfoSource::positionUpdated,
this, &PositionManager::_devicePositionUpdated);
147 connect(source, &QGeoPositionInfoSource::errorOccurred,
this, &PositionManager::_deviceErrorOccurred);
148 connect(source, &QObject::destroyed,
this, [
this]() {
157void PositionManager::_setInternalPositionStatus(SourceStatus status)
159 _platformStatus = status;
168 _setReceiver(producer, sessionId);
171 this, [manager = QPointer<PositionManager>(
this), token = _receiverToken](
const void*) {
173 manager->_retireReceiver(token);
178void PositionManager::_setReceiver(
GPSSourceHealth* producer, quint64 sessionId)
181 _receiver->disconnect(
this);
183 _receiver = producer;
184 _receiverSession = sessionId;
188 connect(producer, &QObject::destroyed,
this, [
this]() { _setReceiver(
nullptr, 0); });
193void PositionManager::_retireReceiver(quint64 token)
195 if (token == _receiverToken) {
196 _setReceiver(
nullptr, 0);
200void PositionManager::_releaseDevice()
202 _setDeviceActive(
false);
204 _device->disconnect(
this);
209void PositionManager::_setDeviceActive(
bool active)
211 active = active && _device;
212 if (_deviceActive == active) {
215 _deviceActive = active;
217 _device->setPreferredPositioningMethods(QGeoPositionInfoSource::SatellitePositioningMethods);
218#if !defined(Q_OS_DARWIN)
219 _device->setUpdateInterval(_device->minimumUpdateInterval());
221 _device->startUpdates();
225 _device->stopUpdates();
229 const QSignalBlocker silent(_deviceHealth);
230 _deviceHealth->
reset();
233void PositionManager::_devicePositionUpdated(
const QGeoPositionInfo& position)
236 if (!_deviceActive) {
241 _setPositioningError(QGeoPositionInfoSource::NoError);
247 if (
position.hasAttribute(QGeoPositionInfo::VerticalAccuracy) && qIsFinite(
position.coordinate().altitude())) {
253void PositionManager::_deviceErrorOccurred(QGeoPositionInfoSource::Error
error)
255 if (!_deviceActive) {
258 if (
error == QGeoPositionInfoSource::AccessError) {
260 }
else if (
error == QGeoPositionInfoSource::ClosedError ||
error == QGeoPositionInfoSource::UnknownSourceError) {
264 _setPositioningError(
error);
267 if (
error != QGeoPositionInfoSource::NoError &&
error != QGeoPositionInfoSource::UpdateTimeoutError) {
273void PositionManager::_bindingChanged(SelectedSource kind)
276 _reselect = _reselect || _selectedKind == kind;
280void PositionManager::_update(SelectedSource reporter)
284 bool republish = reporter == chosen;
285 if (_reselect || chosen != _selectedKind || _currentHealth != health) {
286 qCDebug(PositionManagerLog) <<
"Ground-station position source changed"
287 <<
"source:" << chosen <<
"selected:" << health;
289 _selectedKind = chosen;
290 _currentHealth = health;
291 _selectionRevision = health ? health->observationRevision() : 0;
294 _gcsPositioningError = QGeoPositionInfoSource::NoError;
298 if (republish || (_published.position.isValid() && !_accepted(_selectedKind))) {
317 const qint64 nowMs = _scheduler->
nowMs();
318 if (!_receiverUsableSinceMs) {
319 _receiverUsableSinceMs = nowMs;
322 if (remaining > std::chrono::milliseconds::zero()) {
323 _recoveryTask.
schedule(remaining, [
this]() { _update(); });
327 _receiverUsableSinceMs.reset();
329 if (receiverUsable) {
335 if (_sourceHealth(_selectedKind)) {
336 return _selectedKind;
341void PositionManager::_republish()
344 if (!_currentHealth) {
349 _currentHealth->observationRevision() != _selectionRevision;
353 _gcsPositioningError == QGeoPositionInfoSource::NoError) {
354 _setPositioningError(QGeoPositionInfoSource::UpdateTimeoutError);
358 _setPositioningError(QGeoPositionInfoSource::NoError);
359 _published = {.position = accepted->position.coordinate(),
360 .heading = accepted->heading(),
361 .horizontalAccuracy = accepted->position.attribute(QGeoPositionInfo::HorizontalAccuracy)};
364void PositionManager::_updateStatus()
368 if (_currentHealth) {
369 switch (_currentHealth->state()) {
386 status = _platformStatus;
390 status = _platformStatus;
392 _sourceStatus = status;
395void PositionManager::_emitChanges()
398 const Notified::Selection selection{_selectedSource, _sourceStatus,
selectedSourceName()};
399 if (std::exchange(_notified.selection, selection) != selection) {
402 const qreal accuracy = _published.horizontalAccuracy;
403 if (std::exchange(_notified.publication.horizontalAccuracy, accuracy) != accuracy) {
406 const qreal heading = _published.heading;
407 const qreal notifiedHeading = std::exchange(_notified.publication.heading, heading);
408 if (notifiedHeading != heading && !(qIsNaN(notifiedHeading) && qIsNaN(heading))) {
411 const QGeoCoordinate
position = _published.position;
412 if (std::exchange(_notified.publication.position, position) != position) {
417void PositionManager::_setPositioningError(QGeoPositionInfoSource::Error
error)
419 if (_gcsPositioningError ==
error) {
422 _gcsPositioningError =
error;
423 if (
error != QGeoPositionInfoSource::NoError) {
424 qCDebug(PositionManagerLog) <<
"Positioning error:" <<
error;
431 return _currentHealth && _authorized ? _accepted(_selectedKind, use, maximumAge) : std::nullopt;
435 std::optional<std::chrono::milliseconds> maximumAge)
const
440 observation->
sessionId != _receiverSession) {
446GPSSourceHealth* PositionManager::_sourceHealth(SelectedSource kind)
const
450 return _receiver.data();
452 return _device ? _deviceHealth :
nullptr;
461 switch (_selectedSource) {
465 return tr(
"GNSS receiver");
467 return _usingPluginSource ? tr(
"Plugin positioning") : tr(
"Internal positioning");
474 switch (_sourceStatus) {
476 return tr(
"Selected position source is not connected");
478 return tr(
"Waiting for location permission");
480 return tr(
"Location permission denied");
482 return tr(
"No internal positioning backend is available");
484 return tr(
"Waiting for a position fix");
486 return tr(
"Position is usable");
488 return tr(
"Position data is stale");
490 return tr(
"Position fix does not meet accuracy requirements");
std::shared_ptr< const void > GPSPositionSourceRegistration
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Session health is independent of transport readiness and RTK survey-in validity.
void invalidatePosition()
void updateObservation(const GPSObservation &observation)
std::optional< GPSObservation > acceptedObservation(GPSObservation::PositionUse use=GPSObservation::PositionUse::GroundStation, std::optional< std::chrono::milliseconds > maximumAge=std::nullopt) const
void setInternalPositionSource(QGeoPositionInfoSource *source, SourceStatus status, bool custom=false, GPSAltitudeDatum altitudeDatum=GPSAltitudeDatum::Unknown)
QString sourceStatusText() const
~PositionManager() override
void gcsPositionChanged(QGeoCoordinate gcsPosition)
void gcsPositionHorizontalAccuracyChanged(qreal gcsPositionHorizontalAccuracy)
void shutdown()
Releases the position sources and ignores later configuration; init() does not restart them.
std::optional< GPSObservation > acceptedObservation(GPSObservation::PositionUse use=GPSObservation::PositionUse::GroundStation, std::optional< std::chrono::milliseconds > maximumAge=std::nullopt) const
void setConfiguration(const Configuration &configuration)
Ignored after shutdown().
PositionManager(QObject *parent=nullptr, RuntimeScheduler *scheduler=nullptr)
static constexpr std::chrono::milliseconds RECOVERY_DELAY
SourceMode
Values of RTKSettings::gcsPositionSource.
void gcsHeadingChanged(qreal gcsHeading)
GPSPositionSourceRegistration registerReceiver(GPSSourceHealth *producer, quint64 sessionId=0)
std::function< QGeoPositionInfoSource *(QObject *parent)> SourceFactory
Creates a position source parented to parent, or returns null.
const Configuration & configuration() const
void setPlatformSourceFactory(SourceFactory factory)
QString selectedSourceName() const
virtual quint64 nowUs() const =0
bool schedule(std::chrono::microseconds delay, RuntimeScheduler::Callback callback)
GPSPositionReport position(const GPSDecodedPosition &source, const GPSIntegrityReport &diagnostic, uint64_t nowUs)
constexpr std::chrono::microseconds remaining(uint64_t timestampUs, uint64_t nowUs, std::chrono::microseconds lifetime)
Time left before a receipt reaches lifetime; zero for a missing, future or expired one.
Receiver-independent data. Unknown metadata remains absent, never a manufactured zero.
QGeoPositionInfo position
quint64 monotonicTimestampUs
GPSAltitudeDatum altitudeDatum