GeoFenceController.cc 19.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/


/// @file
///     @author Don Gagne <don@thegagnes.com>

#include "GeoFenceController.h"
#include "Vehicle.h"
#include "FirmwarePlugin.h"
#include "MAVLinkProtocol.h"
#include "QGCApplication.h"
19 20
#include "ParameterManager.h"
#include "JsonHelper.h"
21
#include "QGCQGeoCoordinate.h"
22
#include "AppSettings.h"
DonLakeFlyer's avatar
DonLakeFlyer committed
23
#include "PlanMasterController.h"
24 25
#include "SettingsManager.h"
#include "AppSettings.h"
Don Gagne's avatar
Don Gagne committed
26 27

#ifndef __mobile__
28
#include "MainWindow.h"
29
#include "QGCQFileDialog.h"
Don Gagne's avatar
Don Gagne committed
30
#endif
31 32

#include <QJsonDocument>
33
#include <QJsonArray>
34 35 36

QGC_LOGGING_CATEGORY(GeoFenceControllerLog, "GeoFenceControllerLog")

37 38
QMap<QString, FactMetaData*> GeoFenceController::_metaDataMap;

39 40
const char* GeoFenceController::_jsonFileTypeValue =        "GeoFence";
const char* GeoFenceController::_jsonBreachReturnKey =      "breachReturn";
41 42 43
const char* GeoFenceController::_jsonPolygonsKey =          "polygons";
const char* GeoFenceController::_jsonCirclesKey =           "circles";

44 45
const char* GeoFenceController::_breachReturnAltitudeFactName = "Altitude";

46
const char* GeoFenceController::_px4ParamCircularFence =    "GF_MAX_HOR_DIST";
47

48
GeoFenceController::GeoFenceController(PlanMasterController* masterController, QObject* parent)
49 50 51 52 53 54
    : PlanElementController         (masterController, parent)
    , _geoFenceManager              (_managerVehicle->geoFenceManager())
    , _dirty                        (false)
    , _breachReturnAltitudeFact     (0, _breachReturnAltitudeFactName, FactMetaData::valueTypeDouble)
    , _breachReturnDefaultAltitude  (qgcApp()->toolbox()->settingsManager()->appSettings()->defaultMissionItemAltitude()->rawValue().toDouble())

55
    , _itemsRequested           (false)
56
    , _px4ParamCircularFenceFact(nullptr)
57
{
58 59 60 61 62 63 64
    if (_metaDataMap.isEmpty()) {
        _metaDataMap = FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/BreachReturn.FactMetaData.json"), nullptr /* metaDataParent */);
    }

    _breachReturnAltitudeFact.setMetaData(_metaDataMap[_breachReturnAltitudeFactName]);
    _breachReturnAltitudeFact.setRawValue(_breachReturnDefaultAltitude);

65 66
    connect(&_polygons, &QmlObjectListModel::countChanged, this, &GeoFenceController::_updateContainsItems);
    connect(&_circles,  &QmlObjectListModel::countChanged, this, &GeoFenceController::_updateContainsItems);
67 68

    managerVehicleChanged(_managerVehicle);
69 70 71 72 73

    connect(this,                       &GeoFenceController::breachReturnPointChanged,  this, &GeoFenceController::_setDirty);
    connect(&_breachReturnAltitudeFact, &Fact::rawValueChanged,                         this, &GeoFenceController::_setDirty);
    connect(&_polygons,                 &QmlObjectListModel::dirtyChanged,              this, &GeoFenceController::_setDirty);
    connect(&_circles,                  &QmlObjectListModel::dirtyChanged,              this, &GeoFenceController::_setDirty);
74 75 76 77 78 79 80
}

GeoFenceController::~GeoFenceController()
{

}

81
void GeoFenceController::start(bool flyView)
82
{
83
    qCDebug(GeoFenceControllerLog) << "start flyView" << flyView;
84

85
    PlanElementController::start(flyView);
86 87 88 89 90
    _init();
}

void GeoFenceController::_init(void)
{
91

92 93 94 95
}

void GeoFenceController::setBreachReturnPoint(const QGeoCoordinate& breachReturnPoint)
{
96 97 98
    if (_breachReturnPoint != breachReturnPoint) {
        _breachReturnPoint = breachReturnPoint;
        setDirty(true);
99 100 101 102
        emit breachReturnPointChanged(breachReturnPoint);
    }
}

103
void GeoFenceController::managerVehicleChanged(Vehicle* managerVehicle)
104
{
105 106
    if (_managerVehicle) {
        _geoFenceManager->disconnect(this);
107
        _managerVehicle->disconnect(this);
108
        _managerVehicle->parameterManager()->disconnect(this);
109 110
        _managerVehicle = nullptr;
        _geoFenceManager = nullptr;
111
    }
112

113 114
    _managerVehicle = managerVehicle;
    if (!_managerVehicle) {
115
        qWarning() << "GeoFenceController::managerVehicleChanged managerVehicle=nullptr";
116 117 118 119
        return;
    }

    _geoFenceManager = _managerVehicle->geoFenceManager();
DonLakeFlyer's avatar
DonLakeFlyer committed
120 121 122
    connect(_geoFenceManager, &GeoFenceManager::loadComplete,                   this, &GeoFenceController::_managerLoadComplete);
    connect(_geoFenceManager, &GeoFenceManager::sendComplete,                   this, &GeoFenceController::_managerSendComplete);
    connect(_geoFenceManager, &GeoFenceManager::removeAllComplete,              this, &GeoFenceController::_managerRemoveAllComplete);
123 124
    connect(_geoFenceManager, &GeoFenceManager::inProgressChanged,              this, &GeoFenceController::syncInProgressChanged);

125 126 127 128
    connect(_managerVehicle,  &Vehicle::capabilityBitsChanged,                  this, &GeoFenceController::supportedChanged);

    connect(_managerVehicle->parameterManager(), &ParameterManager::parametersReadyChanged, this, &GeoFenceController::_parametersReady);
    _parametersReady();
129 130

    emit supportedChanged(supported());
131
}
Don Gagne's avatar
Don Gagne committed
132

133
bool GeoFenceController::load(const QJsonObject& json, QString& errorString)
134
{
DonLakeFlyer's avatar
DonLakeFlyer committed
135 136
    removeAll();

137 138
    errorString.clear();

139 140 141
    if (!json.contains(JsonHelper::jsonVersionKey) ||
            (json.contains(JsonHelper::jsonVersionKey) && json[JsonHelper::jsonVersionKey].toInt() == 1)) {
        // We just ignore old version 1 or prior data
DonLakeFlyer's avatar
DonLakeFlyer committed
142 143 144
        return true;
    }

145 146 147 148
    QList<JsonHelper::KeyValidateInfo> keyInfoList = {
        { JsonHelper::jsonVersionKey,   QJsonValue::Double, true },
        { _jsonCirclesKey,              QJsonValue::Array,  true },
        { _jsonPolygonsKey,             QJsonValue::Array,  true },
149
        { _jsonBreachReturnKey,         QJsonValue::Array,  false },
150 151
    };
    if (!JsonHelper::validateKeys(json, keyInfoList, errorString)) {
152 153 154
        return false;
    }

155 156
    if (json[JsonHelper::jsonVersionKey].toInt() != _jsonCurrentVersion) {
        errorString = tr("GeoFence supports version %1").arg(_jsonCurrentVersion);
157
        return false;
158 159
    }

160
    QJsonArray jsonPolygonArray = json[_jsonPolygonsKey].toArray();
161
    for (const QJsonValue jsonPolygonValue: jsonPolygonArray) {
162 163 164 165 166 167 168 169 170 171 172 173 174
        if (jsonPolygonValue.type() != QJsonValue::Object) {
            errorString = tr("GeoFence polygon not stored as object");
            return false;
        }

        QGCFencePolygon* fencePolygon = new QGCFencePolygon(false /* inclusion */, this /* parent */);
        if (!fencePolygon->loadFromJson(jsonPolygonValue.toObject(), true /* required */, errorString)) {
            return false;
        }
        _polygons.append(fencePolygon);
    }

    QJsonArray jsonCircleArray = json[_jsonCirclesKey].toArray();
175
    for (const QJsonValue jsonCircleValue: jsonCircleArray) {
176 177 178 179 180 181 182 183 184 185 186 187
        if (jsonCircleValue.type() != QJsonValue::Object) {
            errorString = tr("GeoFence circle not stored as object");
            return false;
        }

        QGCFenceCircle* fenceCircle = new QGCFenceCircle(this /* parent */);
        if (!fenceCircle->loadFromJson(jsonCircleValue.toObject(), errorString)) {
            return false;
        }
        _circles.append(fenceCircle);
    }

188 189 190 191 192 193 194 195 196 197 198
    if (json.contains(_jsonBreachReturnKey)) {
        if (!JsonHelper::loadGeoCoordinate(json[_jsonBreachReturnKey], true /* altitudeRequred */, _breachReturnPoint, errorString)) {
            return false;
        }
        _breachReturnAltitudeFact.setRawValue(_breachReturnPoint.altitude());
    } else {
        _breachReturnPoint = QGeoCoordinate();
        _breachReturnAltitudeFact.setRawValue(_breachReturnDefaultAltitude);
    }
    emit breachReturnPointChanged(_breachReturnPoint);

199
    setDirty(false);
200 201

    return true;
202 203
}

204
void GeoFenceController::save(QJsonObject& json)
205
{
206 207 208 209 210 211 212 213
    json[JsonHelper::jsonVersionKey] = _jsonCurrentVersion;

    QJsonArray jsonPolygonArray;
    for (int i=0; i<_polygons.count(); i++) {
        QJsonObject jsonPolygon;
        QGCFencePolygon* fencePolygon = _polygons.value<QGCFencePolygon*>(i);
        fencePolygon->saveToJson(jsonPolygon);
        jsonPolygonArray.append(jsonPolygon);
214
    }
215 216 217 218 219 220 221 222 223 224
    json[_jsonPolygonsKey] = jsonPolygonArray;

    QJsonArray jsonCircleArray;
    for (int i=0; i<_circles.count(); i++) {
        QJsonObject jsonCircle;
        QGCFenceCircle* fenceCircle = _circles.value<QGCFenceCircle*>(i);
        fenceCircle->saveToJson(jsonCircle);
        jsonCircleArray.append(jsonCircle);
    }
    json[_jsonCirclesKey] = jsonCircleArray;
225 226 227 228 229 230 231 232

    if (_breachReturnPoint.isValid()) {
        QJsonValue jsonCoordinate;

        _breachReturnPoint.setAltitude(_breachReturnAltitudeFact.rawValue().toDouble());
        JsonHelper::saveGeoCoordinate(_breachReturnPoint, true /* writeAltitude */, jsonCoordinate);
        json[_jsonBreachReturnKey] = jsonCoordinate;
    }
233 234
}

235
void GeoFenceController::removeAll(void)
236
{    
237
    setBreachReturnPoint(QGeoCoordinate());
238 239
    _polygons.clearAndDeleteContents();
    _circles.clearAndDeleteContents();
240 241
}

DonLakeFlyer's avatar
DonLakeFlyer committed
242 243 244 245 246 247 248 249 250 251 252
void GeoFenceController::removeAllFromVehicle(void)
{
    if (_masterController->offline()) {
        qCWarning(GeoFenceControllerLog) << "GeoFenceController::removeAllFromVehicle called while offline";
    } else if (syncInProgress()) {
        qCWarning(GeoFenceControllerLog) << "GeoFenceController::removeAllFromVehicle called while syncInProgress";
    } else {
        _geoFenceManager->removeAll();
    }
}

253 254
void GeoFenceController::loadFromVehicle(void)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
255 256 257 258
    if (_masterController->offline()) {
        qCWarning(GeoFenceControllerLog) << "GeoFenceController::loadFromVehicle called while offline";
    } else if (syncInProgress()) {
        qCWarning(GeoFenceControllerLog) << "GeoFenceController::loadFromVehicle called while syncInProgress";
259
    } else {
DonLakeFlyer's avatar
DonLakeFlyer committed
260 261
        _itemsRequested = true;
        _geoFenceManager->loadFromVehicle();
262 263 264 265 266
    }
}

void GeoFenceController::sendToVehicle(void)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
267 268 269 270 271
    if (_masterController->offline()) {
        qCWarning(GeoFenceControllerLog) << "GeoFenceController::sendToVehicle called while offline";
    } else if (syncInProgress()) {
        qCWarning(GeoFenceControllerLog) << "GeoFenceController::sendToVehicle called while syncInProgress";
    } else {
DonLakeFlyer's avatar
DonLakeFlyer committed
272
        qCDebug(GeoFenceControllerLog) << "GeoFenceController::sendToVehicle";
273
        _geoFenceManager->sendToVehicle(_breachReturnPoint, _polygons, _circles);
DonLakeFlyer's avatar
DonLakeFlyer committed
274 275
        setDirty(false);
    }
276 277 278 279
}

bool GeoFenceController::syncInProgress(void) const
{
280
    return _geoFenceManager->inProgress();
281 282 283 284
}

bool GeoFenceController::dirty(void) const
{
285
    return _dirty;
286 287 288 289 290 291 292 293
}


void GeoFenceController::setDirty(bool dirty)
{
    if (dirty != _dirty) {
        _dirty = dirty;
        if (!dirty) {
294 295 296 297 298 299 300 301
            for (int i=0; i<_polygons.count(); i++) {
                QGCFencePolygon* polygon = _polygons.value<QGCFencePolygon*>(i);
                polygon->setDirty(false);
            }
            for (int i=0; i<_circles.count(); i++) {
                QGCFenceCircle* circle = _circles.value<QGCFenceCircle*>(i);
                circle->setDirty(false);
            }
302 303 304 305 306 307 308 309 310 311 312
        }
        emit dirtyChanged(dirty);
    }
}

void GeoFenceController::_polygonDirtyChanged(bool dirty)
{
    if (dirty) {
        setDirty(true);
    }
}
313 314 315 316 317 318

void GeoFenceController::_setDirty(void)
{
    setDirty(true);
}

319 320
void GeoFenceController::_setFenceFromManager(const QList<QGCFencePolygon>& polygons,
                                              const QList<QGCFenceCircle>&  circles)
321
{
322 323 324 325 326
    _polygons.clearAndDeleteContents();
    _circles.clearAndDeleteContents();

    for (int i=0; i<polygons.count(); i++) {
        _polygons.append(new QGCFencePolygon(polygons[i], this));
327
    }
328 329 330 331 332 333

    for (int i=0; i<circles.count(); i++) {
        _circles.append(new QGCFenceCircle(circles[i], this));
    }

    setDirty(false);
334 335 336 337 338 339
}

void GeoFenceController::_setReturnPointFromManager(QGeoCoordinate breachReturnPoint)
{
    _breachReturnPoint = breachReturnPoint;
    emit breachReturnPointChanged(_breachReturnPoint);
340 341 342 343 344
    if (_breachReturnPoint.isValid()) {
        _breachReturnAltitudeFact.setRawValue(_breachReturnPoint.altitude());
    } else {
        _breachReturnAltitudeFact.setRawValue(_breachReturnDefaultAltitude);
    }
345 346
}

347
void GeoFenceController::_managerLoadComplete(void)
348
{
DonLakeFlyer's avatar
DonLakeFlyer committed
349 350
    // Fly view always reloads on _loadComplete
    // Plan view only reloads on _loadComplete if specifically requested
351
    if (_flyView || _itemsRequested) {
352 353
        _setReturnPointFromManager(_geoFenceManager->breachReturnPoint());
        _setFenceFromManager(_geoFenceManager->polygons(), _geoFenceManager->circles());
DonLakeFlyer's avatar
DonLakeFlyer committed
354 355 356 357 358 359
        setDirty(false);
        emit loadComplete();
    }
    _itemsRequested = false;
}

360
void GeoFenceController::_managerSendComplete(bool error)
DonLakeFlyer's avatar
DonLakeFlyer committed
361 362
{
    // Fly view always reloads on manager sendComplete
363
    if (!error && _flyView) {
DonLakeFlyer's avatar
DonLakeFlyer committed
364 365 366 367
        showPlanFromManagerVehicle();
    }
}

368
void GeoFenceController::_managerRemoveAllComplete(bool error)
DonLakeFlyer's avatar
DonLakeFlyer committed
369
{
370 371 372 373
    if (!error) {
        // Remove all from vehicle so we always update
        showPlanFromManagerVehicle();
    }
374
}
375

376 377
bool GeoFenceController::containsItems(void) const
{
378
    return _polygons.count() > 0 || _circles.count() > 0;
379 380 381 382 383 384 385
}

void GeoFenceController::_updateContainsItems(void)
{
    emit containsItemsChanged(containsItems());
}

DonLakeFlyer's avatar
DonLakeFlyer committed
386
bool GeoFenceController::showPlanFromManagerVehicle(void)
387
{
388
    qCDebug(GeoFenceControllerLog) << "showPlanFromManagerVehicle _flyView" << _flyView;
DonLakeFlyer's avatar
DonLakeFlyer committed
389 390
    if (_masterController->offline()) {
        qCWarning(GeoFenceControllerLog) << "GeoFenceController::showPlanFromManagerVehicle called while offline";
Patrick José Pereira's avatar
Patrick José Pereira committed
391
        return true;    // stops further propagation of showPlanFromManagerVehicle due to error
DonLakeFlyer's avatar
DonLakeFlyer committed
392 393 394
    } else {
        _itemsRequested = true;
        if (!_managerVehicle->initialPlanRequestComplete()) {
DonLakeFlyer's avatar
DonLakeFlyer committed
395
            // The vehicle hasn't completed initial load, we can just wait for loadComplete to be signalled automatically
DonLakeFlyer's avatar
DonLakeFlyer committed
396 397 398 399 400 401 402 403 404 405
            qCDebug(GeoFenceControllerLog) << "showPlanFromManagerVehicle: !initialPlanRequestComplete, wait for signal";
            return true;
        } else if (syncInProgress()) {
            // If the sync is already in progress, _loadComplete will be called automatically when it is done. So no need to do anything.
            qCDebug(GeoFenceControllerLog) << "showPlanFromManagerVehicle: syncInProgress wait for signal";
            return true;
        } else {
            // Fake a _loadComplete with the current items
            qCDebug(GeoFenceControllerLog) << "showPlanFromManagerVehicle: sync complete simulate signal";
            _itemsRequested = true;
406
            _managerLoadComplete();
DonLakeFlyer's avatar
DonLakeFlyer committed
407 408 409
            return false;
        }
    }
410
}
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499

void GeoFenceController::addInclusionPolygon(QGeoCoordinate topLeft, QGeoCoordinate bottomRight)
{
    QGeoCoordinate topRight(topLeft.latitude(), bottomRight.longitude());
    QGeoCoordinate bottomLeft(bottomRight.latitude(), topLeft.longitude());

    double halfWidthMeters = topLeft.distanceTo(topRight) / 2.0;
    double halfHeightMeters = topLeft.distanceTo(bottomLeft) / 2.0;

    QGeoCoordinate centerLeftEdge = topLeft.atDistanceAndAzimuth(halfHeightMeters, 180);
    QGeoCoordinate centerTopEdge = topLeft.atDistanceAndAzimuth(halfWidthMeters, 90);
    QGeoCoordinate center(centerLeftEdge.latitude(), centerTopEdge.longitude());

    // Initial polygon is inset to take 3/4s of viewport with max width/height of 3000 meters
    halfWidthMeters =   qMin(halfWidthMeters * 0.75, 1500.0);
    halfHeightMeters =  qMin(halfHeightMeters * 0.75, 1500.0);

    // Initial polygon has max width and height of 3000 meters
    topLeft =           center.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 0);
    topRight =          center.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 0);
    bottomLeft =        center.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 180);
    bottomRight =       center.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 180);

    QGCFencePolygon* polygon = new QGCFencePolygon(true /* inclusion */, this);
    polygon->appendVertex(topLeft);
    polygon->appendVertex(topRight);
    polygon->appendVertex(bottomRight);
    polygon->appendVertex(bottomLeft);
    _polygons.append(polygon);

    clearAllInteractive();
    polygon->setInteractive(true);
}

void GeoFenceController::addInclusionCircle(QGeoCoordinate topLeft, QGeoCoordinate bottomRight)
{
    QGeoCoordinate topRight(topLeft.latitude(), bottomRight.longitude());
    QGeoCoordinate bottomLeft(bottomRight.latitude(), topLeft.longitude());

    // Initial radius is inset to take 3/4s of viewport and max of 1500 meters
    double halfWidthMeters = topLeft.distanceTo(topRight) / 2.0;
    double halfHeightMeters = topLeft.distanceTo(bottomLeft) / 2.0;
    double radius = qMin(qMin(halfWidthMeters, halfHeightMeters) * 0.75, 1500.0);

    QGeoCoordinate centerLeftEdge = topLeft.atDistanceAndAzimuth(halfHeightMeters, 180);
    QGeoCoordinate centerTopEdge = topLeft.atDistanceAndAzimuth(halfWidthMeters, 90);
    QGeoCoordinate center(centerLeftEdge.latitude(), centerTopEdge.longitude());

    QGCFenceCircle* circle = new QGCFenceCircle(center, radius, true /* inclusion */, this);
    _circles.append(circle);

    clearAllInteractive();
    circle->setInteractive(true);
}

void GeoFenceController::deletePolygon(int index)
{
    if (index < 0 || index > _polygons.count() - 1) {
        return;
    }

    QGCFencePolygon* polygon = qobject_cast<QGCFencePolygon*>(_polygons.removeAt(index));
    polygon->deleteLater();
}

void GeoFenceController::deleteCircle(int index)
{
    if (index < 0 || index > _circles.count() - 1) {
        return;
    }

    QGCFenceCircle* circle = qobject_cast<QGCFenceCircle*>(_circles.removeAt(index));
    circle->deleteLater();
}

void GeoFenceController::clearAllInteractive(void)
{
    for (int i=0; i<_polygons.count(); i++) {
        _polygons.value<QGCFencePolygon*>(i)->setInteractive(false);
    }
    for (int i=0; i<_circles.count(); i++) {
        _circles.value<QGCFenceCircle*>(i)->setInteractive(false);
    }
}

bool GeoFenceController::supported(void) const
{
    return (_managerVehicle->capabilityBits() & MAV_PROTOCOL_CAPABILITY_MISSION_FENCE) && (_managerVehicle->maxProtoVersion() >= 200);
}
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514

// Hack for PX4
double GeoFenceController::paramCircularFence(void)
{
    if (_managerVehicle->isOfflineEditingVehicle() || !_managerVehicle->parameterManager()->parameterExists(FactSystem::defaultComponentId, _px4ParamCircularFence)) {
        return 0;
    }

    return _managerVehicle->parameterManager()->getParameter(FactSystem::defaultComponentId, _px4ParamCircularFence)->rawValue().toDouble();
}

void GeoFenceController::_parametersReady(void)
{
    if (_px4ParamCircularFenceFact) {
        _px4ParamCircularFenceFact->disconnect(this);
515
        _px4ParamCircularFenceFact = nullptr;
516 517 518 519 520 521 522 523 524 525 526
    }

    if (_managerVehicle->isOfflineEditingVehicle() || !_managerVehicle->parameterManager()->parameterExists(FactSystem::defaultComponentId, _px4ParamCircularFence)) {
        emit paramCircularFenceChanged();
        return;
    }

    _px4ParamCircularFenceFact = _managerVehicle->parameterManager()->getParameter(FactSystem::defaultComponentId, _px4ParamCircularFence);
    connect(_px4ParamCircularFenceFact, &Fact::rawValueChanged, this, &GeoFenceController::paramCircularFenceChanged);
    emit paramCircularFenceChanged();
}