QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCCameraManager.cc
Go to the documentation of this file.
1#include "QGCCameraManager.h"
2#include "CameraMetaData.h"
3#include "FirmwarePlugin.h"
4#include "Joystick.h"
5#include "JoystickManager.h"
6#include "MAVLinkLib.h"
10#include "QGCVideoStreamInfo.h"
12#include "Vehicle.h"
13
14#include <cmath>
16#include "SettingsManager.h"
17#include <numbers>
18
19constexpr double kPi = std::numbers::pi_v<double>;
20
21QGC_LOGGING_CATEGORY(CameraManagerLog, "Camera.QGCCameraManager")
22
23namespace {
24 constexpr int kHeartbeatTickMs = 500;
25 constexpr int kSilentTimeoutMs = 5000;
26 constexpr int kMaxRetryCount = 10;
27}
28
29QVariantList QGCCameraManager::_cameraList;
30
32 void* user,
33 MAV_RESULT result,
35 const mavlink_message_t& message)
36{
37 auto* mgr = static_cast<QGCCameraManager*>(user);
38
39 if (result != MAV_RESULT_ACCEPTED) {
40 qCDebug(CameraManagerLog) << "CAMERA_FOV_STATUS request failed, result:"
41 << result << "failure:" << failureCode;
42 return;
43 }
44
45 if (message.msgid != MAVLINK_MSG_ID_CAMERA_FOV_STATUS) {
46 qCDebug(CameraManagerLog) << "Unexpected msg id:" << message.msgid;
47 return;
48 }
49
50 mavlink_camera_fov_status_t fov{};
51
52 mavlink_msg_camera_fov_status_decode(&message, &fov);
53
54 if (!mgr) return;
55}
56
57/*===========================================================================*/
58
60 : compID(compID_)
61 , vehicle(vehicle_)
62 , manager(manager_)
63{
64 qCDebug(CameraManagerLog) << this;
65 backoffTimer.setSingleShot(true);
66}
67
69{
70 qCDebug(CameraManagerLog) << this;
71}
72
73/*===========================================================================*/
74
76 : QObject(vehicle)
77 , _vehicle(vehicle)
78 , _simulatedCameraControl(new SimulatedCameraControl(vehicle, this))
79{
80 qCDebug(CameraManagerLog) << this;
81
82 (void) qRegisterMetaType<CameraMetaData*>("CameraMetaData*");
83
84 _addCameraControlToLists(_simulatedCameraControl);
85
86 (void) connect(_vehicle, &Vehicle::initialConnectComplete, this, &QGCCameraManager::_initialConnectCompleted, Qt::UniqueConnection);
89 (void) connect(&_camerasLostHeartbeatTimer, &QTimer::timeout, this, &QGCCameraManager::_checkForLostCameras);
90
91 _camerasLostHeartbeatTimer.setSingleShot(false);
92 _lastZoomChange.start();
93 _lastFocusChange.start();
94 _lastCameraChange.start();
95 _camerasLostHeartbeatTimer.start(kHeartbeatTickMs);
96}
97
98void QGCCameraManager::_initialConnectCompleted()
99{
100 _initialConnectComplete = true;
101}
102
104{
105 // Stop all camera info request timers and clean up
106 for (auto* cameraInfo : _cameraInfoRequest) {
107 cameraInfo->backoffTimer.stop();
108 QObject::disconnect(&cameraInfo->backoffTimer, nullptr, nullptr, nullptr);
109 }
110 qDeleteAll(_cameraInfoRequest);
111 _cameraInfoRequest.clear();
112
113 qDeleteAll(_cameraInfoContexts);
114 _cameraInfoContexts.clear();
115
116 // Stop the main heartbeat timer
117 _camerasLostHeartbeatTimer.stop();
118
119 qCDebug(CameraManagerLog) << this;
120}
121
123{
124 CameraInfoRequestContext*& context = _cameraInfoContexts[compId];
125 if (!context) {
126 context = new CameraInfoRequestContext{this, compId};
127 }
128 return context;
129}
130
132{
133 if ((sel != _currentCameraIndex) && (sel >= 0) && (sel < _cameras.count())) {
134 _currentCameraIndex = sel;
136 emit streamChanged();
137 }
138}
139
141{
142 qCDebug(CameraManagerLog) << ready;
143 if (!ready) {
144 return;
145 }
146 if (MultiVehicleManager::instance()->activeVehicle() != _vehicle) {
147 return;
148 }
149
150 _vehicleReadyState = true;
153}
154
156{
157 if (!_initialConnectComplete) {
158 return;
159 }
160
161 // Only pay attention to camera components (MAV_COMP_ID_CAMERA..CAMERA6)
162 // and camera-related messages proxied by the autopilot.
163 const bool fromAutopilot = message.compid == MAV_COMP_ID_AUTOPILOT1;
164 const bool fromCamera = (message.compid >= MAV_COMP_ID_CAMERA) && (message.compid <= MAV_COMP_ID_CAMERA6);
165 if ((message.sysid == _vehicle->id()) && (fromAutopilot || fromCamera)) {
166 switch (message.msgid) {
167 case MAVLINK_MSG_ID_CAMERA_CAPTURE_STATUS:
168 _handleCameraCaptureStatus(message);
169 break;
170 case MAVLINK_MSG_ID_STORAGE_INFORMATION:
171 _handleStorageInformation(message);
172 break;
173 case MAVLINK_MSG_ID_HEARTBEAT:
174 // Autopilot heartbeats should not be treated as camera discovery.
175 // Only actual camera component heartbeats should start CAMERA_INFORMATION requests.
176 if (fromCamera) {
177 _handleHeartbeat(message);
178 }
179 break;
180 case MAVLINK_MSG_ID_CAMERA_INFORMATION:
181 _handleCameraInfo(message);
182 break;
183 case MAVLINK_MSG_ID_CAMERA_SETTINGS:
184 _handleCameraSettings(message);
185 break;
186 case MAVLINK_MSG_ID_PARAM_EXT_ACK:
187 _handleParamExtAck(message);
188 break;
189 case MAVLINK_MSG_ID_PARAM_EXT_VALUE:
190 _handleParamExtValue(message);
191 break;
192 case MAVLINK_MSG_ID_VIDEO_STREAM_INFORMATION:
193 _handleVideoStreamInformation(message);
194 break;
195 case MAVLINK_MSG_ID_VIDEO_STREAM_STATUS:
196 _handleVideoStreamStatus(message);
197 break;
198 case MAVLINK_MSG_ID_BATTERY_STATUS:
199 _handleBatteryStatus(message);
200 break;
201 case MAVLINK_MSG_ID_CAMERA_TRACKING_IMAGE_STATUS:
202 _handleTrackingImageStatus(message);
203 break;
204 case MAVLINK_MSG_ID_CAMERA_FOV_STATUS:
205 _handleCameraFovStatus(message);
206 break;
207 default:
208 break;
209 }
210 }
211}
212
213void QGCCameraManager::_handleHeartbeat(const mavlink_message_t &message)
214{
215 const QString sCompID = QString::number(message.compid);
216
217 if (!_cameraInfoRequest.contains(sCompID)) {
218 qCDebug(CameraManagerLog) << "Heartbeat from" << QGCMAVLink::compIdToString(message.compid);
219 CameraStruct *pInfo = new CameraStruct(this, message.compid, _vehicle);
220 pInfo->lastHeartbeat.start();
221 _cameraInfoRequest[sCompID] = pInfo;
222 _requestCameraInfo(pInfo);
223 return;
224 }
225
226 CameraStruct *pInfo = _cameraInfoRequest[sCompID];
227 if (!pInfo) {
228 qCWarning(CameraManagerLog) << sCompID << "is null";
229 return;
230 }
231
232 if (pInfo->infoReceived) {
233 pInfo->lastHeartbeat.start();
234 return;
235 }
236
237 if (pInfo->lastHeartbeat.elapsed() > kSilentTimeoutMs) {
238 qCDebug(CameraManagerLog) << "Camera" << QGCMAVLink::compIdToString(message.compid) << "reappeared after being silent. Resetting retry count and requesting info.";
239 pInfo->retryCount = 0;
240 pInfo->backoffTimer.stop();
241 pInfo->lastHeartbeat.start();
242 _requestCameraInfo(pInfo);
243 return;
244 }
245
246 pInfo->lastHeartbeat.start();
247}
248
250{
251 if ((_currentCameraIndex < _cameras.count()) && !_cameras.isEmpty()) {
252 MavlinkCameraControlInterface *pCamera = qobject_cast<MavlinkCameraControlInterface*>(_cameras[_currentCameraIndex]);
253 return pCamera;
254 }
255 return nullptr;
256}
257
259{
261 if (pCamera) {
262 QGCVideoStreamInfo *pInfo = pCamera->currentStreamInstance();
263 return pInfo;
264 }
265 return nullptr;
266}
267
269{
271 if (pCamera) {
272 QGCVideoStreamInfo *pInfo = pCamera->thermalStreamInstance();
273 return pInfo;
274 }
275 return nullptr;
276}
277
278MavlinkCameraControlInterface *QGCCameraManager::_findCamera(int id)
279{
280 for (int i = 0; i < _cameras.count(); i++) {
281 if (!_cameras[i]) {
282 continue;
283 }
284 MavlinkCameraControlInterface *pCamera = qobject_cast<MavlinkCameraControlInterface*>(_cameras[i]);
285 if (!pCamera) {
286 qCCritical(CameraManagerLog) << "Invalid MavlinkCameraControlInterface instance";
287 continue;
288 }
289 if (pCamera->compID() == id) {
290 return pCamera;
291 }
292 }
293
294 // qCWarning(CameraManagerLog) << "Camera component id not found:" << id;
295 return nullptr;
296}
297
298void QGCCameraManager::_addCameraControlToLists(MavlinkCameraControlInterface *cameraControl)
299{
300 if (qobject_cast<SimulatedCameraControl*>(cameraControl)) {
301 qCDebug(CameraManagerLog) << "Adding simulated camera to list";
302 } else {
303 qCDebug(CameraManagerLog) << "Adding real camera to list - simulated camera will be removed if present";
304 }
305
306 _cameras.append(cameraControl);
307 _cameraLabels.append(cameraControl->modelName());
308 emit camerasChanged();
309 emit cameraLabelsChanged();
310
311 // If simulated camera is in list, remove it when a real camera appears
312 if ((_cameras.count() == 2) && (_cameras[0] == _simulatedCameraControl)) {
313 (void) _cameras.removeAt(0);
314 (void) _cameraLabels.removeAt(0);
315 emit camerasChanged();
316 emit cameraLabelsChanged();
318 }
319}
320
321void QGCCameraManager::_handleCameraInfo(const mavlink_message_t& message)
322{
323 const QString sCompID = QString::number(message.compid);
324 if (!_cameraInfoRequest.contains(sCompID)) {
325 qCDebug(CameraManagerLog) << "Ignoring - Camera info not requested for component" << QGCMAVLink::compIdToString(message.compid);
326 return;
327 }
328 if (_cameraInfoRequest[sCompID]->infoReceived) {
329 qCDebug(CameraManagerLog) << "Ignoring - Already received camera info for component" << QGCMAVLink::compIdToString(message.compid);
330 return;
331 }
332
334 mavlink_msg_camera_information_decode(&message, &info);
335 qCDebug(CameraManagerLog) << "Camera information received from" << QGCMAVLink::compIdToString(message.compid)
336 << "Model:" << reinterpret_cast<const char*>(info.model_name);
337 qCDebug(CameraManagerLog) << "Creating MavlinkCameraControlInterface for camera";
338
339 MavlinkCameraControlInterface *pCamera = _vehicle->firmwarePlugin()->createCameraControl(&info, _vehicle, message.compid, this);
340 if (pCamera) {
341 _addCameraControlToLists(pCamera);
342
343 _cameraInfoRequest[sCompID]->infoReceived = true;
344 _cameraInfoRequest[sCompID]->retryCount = 0;
345 _cameraInfoRequest[sCompID]->backoffTimer.stop();
346 qCDebug(CameraManagerLog) << "Success for compId" << QGCMAVLink::compIdToString(message.compid) << "- reset retry counter";
347 }
348
349 double aspect = std::numeric_limits<double>::quiet_NaN();
350
351 if (info.resolution_h > 0 && info.resolution_v > 0) {
352 aspect = double(info.resolution_v) / double(info.resolution_h);
353 } else if (info.sensor_size_h > 0.f && info.sensor_size_v > 0.f) {
354 aspect = double(info.sensor_size_v) / double(info.sensor_size_h);
355 }
356
357 _aspectByCompId.insert(message.compid, aspect);
358}
359
361{
362 QList<QString> stale;
363 for (auto it = _cameraInfoRequest.cbegin(), end = _cameraInfoRequest.cend(); it != end; ++it) {
364 const auto *info = it.value();
365 if (info && info->infoReceived && (info->lastHeartbeat.elapsed() > kSilentTimeoutMs)) {
366 stale.push_back(it.key());
367 }
368 }
369 if (stale.isEmpty()) {
370 return;
371 }
372
373 bool removedAny = false;
374 for (const QString& key : std::as_const(stale)) {
375 CameraStruct* pInfo = _cameraInfoRequest.take(key);
376 if (!pInfo) {
377 continue;
378 }
379
380 MavlinkCameraControlInterface* pCamera = _findCamera(pInfo->compID);
381 if (pCamera) {
382 const int idx = _cameras.indexOf(pCamera);
383 if (idx >= 0) {
384 qCDebug(CameraManagerLog) << "Removing lost camera" << QGCMAVLink::compIdToString(pInfo->compID);
385 removedAny = true;
386 (void) _cameraLabels.removeAt(idx);
387 (void) _cameras.removeAt(idx);
388 pCamera->deleteLater();
389 }
390 }
391
392 delete pInfo;
393 }
394
395 if (!removedAny) {
396 return;
397 }
398
399 if (_cameras.isEmpty()) {
400 _addCameraControlToLists(_simulatedCameraControl);
401 }
402
403 emit cameraLabelsChanged();
404 emit camerasChanged();
405
406 if (_currentCameraIndex != 0) {
407 _currentCameraIndex = 0;
409 }
410 emit streamChanged();
411}
412
413void QGCCameraManager::_handleCameraCaptureStatus(const mavlink_message_t &message)
414{
415 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
416 if (pCamera) {
417 mavlink_camera_capture_status_t cap{};
418 mavlink_msg_camera_capture_status_decode(&message, &cap);
419 pCamera->handleCameraCaptureStatus(cap);
420 }
421}
422
423void QGCCameraManager::_handleStorageInformation(const mavlink_message_t &message)
424{
425 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
426 if (pCamera) {
427 mavlink_storage_information_t st{};
428 mavlink_msg_storage_information_decode(&message, &st);
429 pCamera->handleStorageInformation(st);
430 }
431}
432
433void QGCCameraManager::_handleCameraSettings(const mavlink_message_t& message)
434{
435 auto pCamera = _findCamera(message.compid);
436 if (pCamera) {
437 mavlink_camera_settings_t settings{};
438 mavlink_msg_camera_settings_decode(&message, &settings);
439 pCamera->handleCameraSettings(settings);
440
441 const int newZoom = static_cast<int>(settings.zoomLevel);
442 if (QThread::currentThread() == thread()) {
443 _setCurrentZoomLevel(newZoom);
444 } else {
445 QMetaObject::invokeMethod(
446 this,
447 "_setCurrentZoomLevel",
448 Qt::QueuedConnection,
449 Q_ARG(int, newZoom)
450 );
451 }
452
453 requestCameraFovForComp(message.compid);
454 }
455}
456
457void QGCCameraManager::_handleParamExtAck(const mavlink_message_t &message)
458{
459 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
460 if (pCamera) {
461 mavlink_param_ext_ack_t ack{};
462 mavlink_msg_param_ext_ack_decode(&message, &ack);
463 pCamera->handleParamExtAck(ack);
464 }
465}
466
467void QGCCameraManager::_handleParamExtValue(const mavlink_message_t &message)
468{
469 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
470 if (pCamera) {
471 mavlink_param_ext_value_t value{};
472 mavlink_msg_param_ext_value_decode(&message, &value);
473 pCamera->handleParamExtValue(value);
474 }
475}
476
477void QGCCameraManager::_handleVideoStreamInformation(const mavlink_message_t &message)
478{
479 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
480 if (pCamera) {
481 mavlink_video_stream_information_t streamInfo{};
482 mavlink_msg_video_stream_information_decode(&message, &streamInfo);
483 pCamera->handleVideoStreamInformation(streamInfo);
484 emit streamChanged();
485 }
486}
487
488void QGCCameraManager::_handleVideoStreamStatus(const mavlink_message_t &message)
489{
490 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
491 if (pCamera) {
492 mavlink_video_stream_status_t streamStatus{};
493 mavlink_msg_video_stream_status_decode(&message, &streamStatus);
494 pCamera->handleVideoStreamStatus(streamStatus);
495 }
496}
497
498void QGCCameraManager::_handleBatteryStatus(const mavlink_message_t &message)
499{
500 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
501 if (pCamera) {
502 mavlink_battery_status_t batteryStatus{};
503 mavlink_msg_battery_status_decode(&message, &batteryStatus);
504 pCamera->handleBatteryStatus(batteryStatus);
505 }
506}
507
508void QGCCameraManager::_handleTrackingImageStatus(const mavlink_message_t &message)
509{
510 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
511 if (pCamera) {
512 mavlink_camera_tracking_image_status_t tis{};
513 mavlink_msg_camera_tracking_image_status_decode(&message, &tis);
514 pCamera->handleTrackingImageStatus(tis);
515 }
516}
517
519
523{
524 auto *context = static_cast<QGCCameraManager::CameraInfoRequestContext*>(resultHandlerData);
525 if (!context->manager) {
526 return nullptr;
527 }
528
529 QGCCameraManager::CameraStruct *cameraInfo = context->manager->findCameraStruct(context->compID);
530 if (!cameraInfo) {
531 qCDebug(CameraManagerLog) << "Camera info request callback for removed camera. compId" << QGCMAVLink::compIdToString(context->compID);
532 }
533 return cameraInfo;
534}
535
536static void _requestCameraInfoCommandResultHandler(void *resultHandlerData, int /*compId*/, const mavlink_command_ack_t &ack, Vehicle::MavCmdResultFailureCode_t failureCode)
537{
538 QGCCameraManager::CameraStruct *cameraInfo = _cameraStructFromContext(resultHandlerData);
539 if (!cameraInfo) {
540 return;
541 }
542
543 if (ack.result != MAV_RESULT_ACCEPTED) {
544 qCDebug(CameraManagerLog) << "MAV_CMD_REQUEST_CAMERA_INFORMATION failed. compId" << QGCMAVLink::compIdToString(cameraInfo->compID)
545 << "Result:" << QGCMAVLink::mavResultToString(ack.result)
546 << "FailureCode:" << Vehicle::mavCmdResultFailureCodeToString(failureCode)
547 << "retryCount:" << cameraInfo->retryCount;
548 _handleCameraInfoRetry(cameraInfo);
549 }
550}
551
552static void _requestCameraInfoMessageResultHandler(void *resultHandlerData, MAV_RESULT result, Vehicle::RequestMessageResultHandlerFailureCode_t failureCode, [[maybe_unused]] const mavlink_message_t &message)
553{
554 QGCCameraManager::CameraStruct *cameraInfo = _cameraStructFromContext(resultHandlerData);
555 if (!cameraInfo) {
556 return;
557 }
558
559 if (result != MAV_RESULT_ACCEPTED) {
560 qCDebug(CameraManagerLog) << "MAV_CMD_REQUEST_MESSAGE:MAVLINK_MSG_ID_CAMERA_INFORMATION failed. compId" << QGCMAVLink::compIdToString(cameraInfo->compID)
561 << "Result:" << QGCMAVLink::mavResultToString(result)
562 << "FailureCode:" << Vehicle::requestMessageResultHandlerFailureCodeToString(failureCode)
563 << "retryCount:" << cameraInfo->retryCount;
564 _handleCameraInfoRetry(cameraInfo);
565 }
566}
567
569{
570 // Give up after max attempts
571 if (pInfo->retryCount >= kMaxRetryCount) {
572 qCDebug(CameraManagerLog) << "Giving up requesting camera info after" << pInfo->retryCount << "attempts for compId" << QGCMAVLink::compIdToString(pInfo->compID);
573 return;
574 }
575
576 // Alternate between REQUEST_MESSAGE and REQUEST_CAMERA_INFORMATION
577 if ((pInfo->retryCount % 2) == 0) {
578 qCDebug(CameraManagerLog) << "Using MAV_CMD_REQUEST_MESSAGE:CAMERA_INFORMATION for compId" << QGCMAVLink::compIdToString(pInfo->compID);
579 manager->vehicle()->requestMessage(_requestCameraInfoMessageResultHandler, manager->cameraInfoContext(pInfo->compID), pInfo->compID, MAVLINK_MSG_ID_CAMERA_INFORMATION);
580 } else {
581 qCDebug(CameraManagerLog) << "Using MAV_CMD_REQUEST_CAMERA_INFORMATION for compId" << QGCMAVLink::compIdToString(pInfo->compID);
582
583 Vehicle::MavCmdAckHandlerInfo_t ackHandlerInfo{};
585 ackHandlerInfo.resultHandlerData = manager->cameraInfoContext(pInfo->compID);
586 ackHandlerInfo.progressHandler = nullptr;
587 ackHandlerInfo.progressHandlerData = nullptr;
588
589 pInfo->vehicle->sendMavCommandWithHandler(&ackHandlerInfo, pInfo->compID, MAV_CMD_REQUEST_CAMERA_INFORMATION, 1 /* request camera capabilities */);
590 }
591}
592
594{
595 if (!cameraInfo) {
596 return;
597 }
598
599 QGCCameraManager *manager = cameraInfo->manager;
600 if (!manager) {
601 qCDebug(CameraManagerLog) << "manager is unavailable for compId" << QGCMAVLink::compIdToString(cameraInfo->compID);
602 return;
603 }
604
605 cameraInfo->retryCount++;
606
607 // For even attempts >= 2, use exponential backoff
608 if ((cameraInfo->retryCount >= 2) && ((cameraInfo->retryCount % 2) == 0)) {
609 const int delaySeconds = 1 << (cameraInfo->retryCount / 2);
610 const int delayMs = delaySeconds * 1000;
611
612 qCDebug(CameraManagerLog) << "Waiting" << delaySeconds << "seconds before retry for compId" << QGCMAVLink::compIdToString(cameraInfo->compID);
613
614 cameraInfo->backoffTimer.stop();
615 (void) QObject::disconnect(&cameraInfo->backoffTimer, nullptr, nullptr, nullptr);
616
617 // Capture compID by value and look up the struct
618 const uint8_t compId = cameraInfo->compID;
619 QPointer<QGCCameraManager> mgrGuard(manager);
620
621 (void) QObject::connect(&cameraInfo->backoffTimer, &QTimer::timeout,
622 &cameraInfo->backoffTimer, // context ensures timer is still alive
623 [mgrGuard, compId]() {
624 if (!mgrGuard) {
625 return;
626 }
627
628 auto* info = mgrGuard->findCameraStruct(compId);
629 if (info) {
630 _requestCameraInfoHelper(mgrGuard.data(), info);
631 }
632 });
633
634 cameraInfo->backoffTimer.start(delayMs);
635 } else {
636 _requestCameraInfoHelper(manager, cameraInfo);
637 }
638}
639
640void QGCCameraManager::_requestCameraInfo(CameraStruct *pInfo)
641{
642 if (!pInfo) {
643 return;
644 }
645 _requestCameraInfoHelper(this, pInfo);
646}
647
649{
650 qCDebug(CameraManagerLog) << "Joystick changed";
651 if (_activeJoystick) {
652 (void) disconnect(_activeJoystick, &Joystick::stepZoom, this, &QGCCameraManager::_stepZoom);
653 (void) disconnect(_activeJoystick, &Joystick::startContinuousZoom, this, &QGCCameraManager::_startZoom);
654 (void) disconnect(_activeJoystick, &Joystick::stopContinuousZoom, this, &QGCCameraManager::_stopZoom);
655 (void) disconnect(_activeJoystick, &Joystick::stepFocus, this, &QGCCameraManager::_stepFocus);
656 (void) disconnect(_activeJoystick, &Joystick::startContinuousFocus, this, &QGCCameraManager::_startFocus);
657 (void) disconnect(_activeJoystick, &Joystick::stopContinuousFocus, this, &QGCCameraManager::_stopFocus);
658 (void) disconnect(_activeJoystick, &Joystick::stepCamera, this, &QGCCameraManager::_stepCamera);
659 (void) disconnect(_activeJoystick, &Joystick::stepStream, this, &QGCCameraManager::_stepStream);
660 (void) disconnect(_activeJoystick, &Joystick::triggerCamera, this, &QGCCameraManager::_triggerCamera);
661 (void) disconnect(_activeJoystick, &Joystick::startVideoRecord, this, &QGCCameraManager::_startVideoRecording);
662 (void) disconnect(_activeJoystick, &Joystick::stopVideoRecord, this, &QGCCameraManager::_stopVideoRecording);
663 (void) disconnect(_activeJoystick, &Joystick::toggleVideoRecord, this, &QGCCameraManager::_toggleVideoRecording);
664 }
665
666 _activeJoystick = joystick;
667
668 if (_activeJoystick) {
669 (void) connect(_activeJoystick, &Joystick::stepZoom, this, &QGCCameraManager::_stepZoom, Qt::UniqueConnection);
670 (void) connect(_activeJoystick, &Joystick::startContinuousZoom, this, &QGCCameraManager::_startZoom, Qt::UniqueConnection);
671 (void) connect(_activeJoystick, &Joystick::stopContinuousZoom, this, &QGCCameraManager::_stopZoom, Qt::UniqueConnection);
672 (void) connect(_activeJoystick, &Joystick::stepFocus, this, &QGCCameraManager::_stepFocus, Qt::UniqueConnection);
673 (void) connect(_activeJoystick, &Joystick::startContinuousFocus, this, &QGCCameraManager::_startFocus, Qt::UniqueConnection);
674 (void) connect(_activeJoystick, &Joystick::stopContinuousFocus, this, &QGCCameraManager::_stopFocus, Qt::UniqueConnection);
675 (void) connect(_activeJoystick, &Joystick::stepCamera, this, &QGCCameraManager::_stepCamera, Qt::UniqueConnection);
676 (void) connect(_activeJoystick, &Joystick::stepStream, this, &QGCCameraManager::_stepStream, Qt::UniqueConnection);
677 (void) connect(_activeJoystick, &Joystick::triggerCamera, this, &QGCCameraManager::_triggerCamera, Qt::UniqueConnection);
678 (void) connect(_activeJoystick, &Joystick::startVideoRecord, this, &QGCCameraManager::_startVideoRecording, Qt::UniqueConnection);
679 (void) connect(_activeJoystick, &Joystick::stopVideoRecord, this, &QGCCameraManager::_stopVideoRecording, Qt::UniqueConnection);
680 (void) connect(_activeJoystick, &Joystick::toggleVideoRecord, this, &QGCCameraManager::_toggleVideoRecording, Qt::UniqueConnection);
681 }
682}
683
685{
687 if (pCamera) {
688 pCamera->takePhoto();
689 }
690}
691
693{
695 if (pCamera) {
696 pCamera->startVideoRecording();
697 }
698}
699
701{
703 if (pCamera) {
704 pCamera->stopVideoRecording();
705 }
706}
707
709{
711 if (pCamera) {
712 pCamera->toggleVideoRecording();
713 }
714}
715
717{
718 if (_lastZoomChange.elapsed() > 40) {
719 _lastZoomChange.start();
720 qCDebug(CameraManagerLog) << "Step Camera Zoom" << direction;
722 if (pCamera) {
723 pCamera->stepZoom(direction);
724 }
725 }
726}
727
729{
730 qCDebug(CameraManagerLog) << "Start Camera Zoom" << direction;
732 if (pCamera) {
733 pCamera->startZoom(direction);
734 }
735}
736
738{
739 qCDebug(CameraManagerLog) << "Stop Camera Zoom";
741 if (pCamera) {
742 pCamera->stopZoom();
743 }
744}
745
747{
748 if (_lastFocusChange.elapsed() > 40) {
749 _lastFocusChange.start();
750 qCDebug(CameraManagerLog) << "Step Camera Focus" << direction;
752 if (pCamera) {
753 pCamera->stepFocus(direction);
754 }
755 }
756}
757
759{
760 qCDebug(CameraManagerLog) << "Start Camera Focus" << direction;
762 if (pCamera) {
763 pCamera->startFocus(direction);
764 }
765}
766
768{
769 qCDebug(CameraManagerLog) << "Stop Camera Focus";
771 if (pCamera) {
772 pCamera->stopFocus();
773 }
774}
775
777{
778 if (_lastCameraChange.elapsed() > 1000) {
779 _lastCameraChange.start();
780 qCDebug(CameraManagerLog) << "Step Camera" << direction;
781 int camera = _currentCameraIndex + direction;
782 if (camera < 0) {
783 camera = _cameras.count() - 1;
784 } else if (camera >= _cameras.count()) {
785 camera = 0;
786 }
787 setCurrentCamera(camera);
788 }
789}
790
792{
793 if (_lastCameraChange.elapsed() > 1000) {
794 _lastCameraChange.start();
796 if (pCamera) {
797 qCDebug(CameraManagerLog) << "Step Camera Stream" << direction;
798 int stream = pCamera->currentStream() + direction;
799 if (stream < 0) {
800 stream = pCamera->streams()->count() - 1;
801 } else if (stream >= pCamera->streams()->count()) {
802 stream = 0;
803 }
804 pCamera->setCurrentStream(stream);
805 }
806 }
807}
808
809const QVariantList &QGCCameraManager::cameraList() const
810{
811 if (_cameraList.isEmpty()) {
812 const QList<CameraMetaData*> cams = CameraMetaData::parseCameraMetaData();
813 _cameraList.reserve(cams.size());
814
815 for (CameraMetaData *cam : cams) {
816 _cameraList << QVariant::fromValue(cam);
817 }
818 }
819 return _cameraList;
820}
821
823 if (!_vehicle) {
824 qCWarning(CameraManagerLog) << "requestCameraFovForComp: vehicle is null";
825 return;
826 }
827 _vehicle->requestMessage(_requestFovOnZoom_Handler, /*user*/this,
828 compId, MAVLINK_MSG_ID_CAMERA_FOV_STATUS);
829}
830
831//-----------------------------------------------------------------------------
832double QGCCameraManager::aspectForComp(int compId) const {
833 auto it = _aspectByCompId.constFind(compId);
834 return (it == _aspectByCompId.cend())
835 ? std::numeric_limits<double>::quiet_NaN()
836 : it.value();
837}
838
840 if (auto* cam = currentCameraInstance()) {
841 return aspectForComp(cam->compID());
842 }
843 return std::numeric_limits<double>::quiet_NaN();
844}
845void QGCCameraManager::_handleCameraFovStatus(const mavlink_message_t& message)
846{
847 mavlink_camera_fov_status_t fov{};
848 mavlink_msg_camera_fov_status_decode(&message, &fov);
849
850 if (!std::isfinite(fov.hfov) || fov.hfov <= 0.0 || fov.hfov >= 180.0) {
851 return;
852 }
853
854 double aspect = aspectForComp(message.compid);
855 if (!std::isfinite(aspect) || aspect <= 0.0) {
856 aspect = 16.0 / 9.0;
857 }
858
859 const double hfovRad = fov.hfov * kPi / 180.0;
860 const double vfovRad = 2.0 * std::atan(std::tan(hfovRad * 0.5) * aspect);
861 const double vfovDeg = vfovRad * 180.0 / kPi;
862
863 if (!std::isfinite(vfovDeg) || vfovDeg <= 0.0 || vfovDeg >= 180.0) {
864 qCWarning(CameraManagerLog) << "Invalid calculated VFOV:" << vfovDeg
865 << "hfov:" << fov.hfov
866 << "aspect:" << aspect
867 << "compId:" << message.compid;
868 return;
869 }
870
872 settings->cameraHFov()->setRawValue(fov.hfov);
873 settings->cameraVFov()->setRawValue(vfovDeg);
874}
875
876void QGCCameraManager::_setCurrentZoomLevel(int level)
877{
878 if (_zoomValueCurrent == level) {
879 return;
880 }
881 _zoomValueCurrent = level;
883}
884
886{
887 return _zoomValueCurrent;
888}
static void _requestFovOnZoom_Handler(void *user, MAV_RESULT result, Vehicle::RequestMessageResultHandlerFailureCode_t failureCode, const mavlink_message_t &message)
static void _handleCameraInfoRetry(QGCCameraManager::CameraStruct *cameraInfo)
static void _requestCameraInfoMessageResultHandler(void *resultHandlerData, MAV_RESULT result, Vehicle::RequestMessageResultHandlerFailureCode_t failureCode, const mavlink_message_t &message)
static QGCCameraManager::CameraStruct * _cameraStructFromContext(void *resultHandlerData)
static void _requestCameraInfoCommandResultHandler(void *resultHandlerData, int, const mavlink_command_ack_t &ack, Vehicle::MavCmdResultFailureCode_t failureCode)
static void _requestCameraInfoHelper(QGCCameraManager *manager, QGCCameraManager::CameraStruct *pInfo)
constexpr double kPi
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
struct __mavlink_command_ack_t mavlink_command_ack_t
struct __mavlink_camera_information_t mavlink_camera_information_t
Set of meta data which describes a camera available on the vehicle.
static QList< CameraMetaData * > parseCameraMetaData()
virtual MavlinkCameraControlInterface * createCameraControl(const mavlink_camera_information_t *info, Vehicle *vehicle, int compID, QObject *parent=nullptr) const
Camera control.
static JoystickManager * instance()
void activeJoystickChanged(Joystick *joystick)
void triggerCamera()
void stopContinuousFocus()
void startVideoRecord()
void stopVideoRecord()
void stepZoom(int direction)
void startContinuousZoom(int direction)
void stopContinuousZoom()
void stepCamera(int direction)
void stepStream(int direction)
void toggleVideoRecord()
void stepFocus(int direction)
void startContinuousFocus(int direction)
Abstract base class for all camera controls: real and simulated.
virtual Q_INVOKABLE void stopZoom()=0
virtual Q_INVOKABLE void startZoom(int direction)=0
virtual QGCVideoStreamInfo * thermalStreamInstance()=0
virtual QmlObjectListModel * streams()=0
virtual void handleBatteryStatus(const mavlink_battery_status_t &bs)=0
virtual void handleTrackingImageStatus(const mavlink_camera_tracking_image_status_t &trackingImageStatus)=0
virtual Q_INVOKABLE void stepZoom(int direction)=0
virtual Q_INVOKABLE bool takePhoto()=0
virtual Q_INVOKABLE bool toggleVideoRecording()=0
virtual void handleCameraSettings(const mavlink_camera_settings_t &settings)=0
virtual QString modelName() const =0
virtual Q_INVOKABLE bool stopVideoRecording()=0
virtual void handleVideoStreamInformation(const mavlink_video_stream_information_t &videoStreamInformation)=0
virtual void handleStorageInformation(const mavlink_storage_information_t &storageInformation)=0
virtual Q_INVOKABLE void stepFocus(int direction)=0
virtual Q_INVOKABLE void startFocus(int direction)=0
virtual int compID() const =0
virtual void handleParamExtValue(const mavlink_param_ext_value_t &paramExtValue)=0
virtual Q_INVOKABLE bool startVideoRecording()=0
virtual QGCVideoStreamInfo * currentStreamInstance()=0
virtual void handleVideoStreamStatus(const mavlink_video_stream_status_t &videoStreamStatus)=0
virtual void setCurrentStream(int stream)=0
virtual int currentStream() const =0
virtual void handleParamExtAck(const mavlink_param_ext_ack_t &paramExtAck)=0
virtual Q_INVOKABLE void stopFocus()=0
virtual void handleCameraCaptureStatus(const mavlink_camera_capture_status_t &cameraCaptureStatus)=0
static MultiVehicleManager * instance()
void parameterReadyVehicleAvailableChanged(bool parameterReadyVehicleAvailable)
Camera Manager.
void currentZoomLevelChanged()
void setCurrentCamera(int sel)
void _vehicleReady(bool ready)
const QVariantList & cameraList() const
Q_INVOKABLE void requestCameraFovForComp(int compId)
void cameraLabelsChanged()
MavlinkCameraControlInterface * currentCameraInstance()
QGCCameraManager(Vehicle *vehicle)
QGCVideoStreamInfo * currentStreamInstance()
void _stepZoom(int direction)
void _startZoom(int direction)
double aspectForComp(int compId) const
QGCVideoStreamInfo * thermalStreamInstance()
Vehicle * vehicle() const
void _mavlinkMessageReceived(const mavlink_message_t &message)
void currentCameraChanged()
CameraInfoRequestContext * cameraInfoContext(uint8_t compId)
Returns the lazily created, manager-lifetime context for compId.
void _stepFocus(int direction)
int currentZoomLevel() const
void _stepCamera(int direction)
void _activeJoystickChanged(Joystick *joystick)
void _stepStream(int direction)
void _startFocus(int direction)
Encapsulates the contents of a VIDEO_STREAM_INFORMATION message.
void append(QObject *object)
Caller maintains responsibility for object ownership and deletion.
bool isEmpty() const override final
QObject * removeAt(int index)
int count() const override final
int indexOf(const QObject *object)
static SettingsManager * instance()
GimbalControllerSettings * gimbalControllerSettings() const
Creates a simulated Camera Control which supports:
static QString mavCmdResultFailureCodeToString(MavCmdResultFailureCode_t failureCode)
Definition Vehicle.cc:3365
void initialConnectComplete()
FirmwarePlugin * firmwarePlugin()
Provides access to the Firmware Plugin for this Vehicle.
Definition Vehicle.h:448
int id() const
Definition Vehicle.h:429
void mavlinkMessageReceived(const mavlink_message_t &message)
static QString requestMessageResultHandlerFailureCodeToString(RequestMessageResultHandlerFailureCode_t failureCode)
Definition Vehicle.cc:3360
void requestMessage(RequestMessageResultHandler resultHandler, void *resultHandlerData, int compId, int messageId, float param1=0.0f, float param2=0.0f, float param3=0.0f, float param4=0.0f, float param5=0.0f)
Definition Vehicle.cc:2249
void sendMavCommandWithHandler(const MavCmdAckHandlerInfo_t *ackHandlerInfo, int compId, MAV_CMD command, float param1=0.0f, float param2=0.0f, float param3=0.0f, float param4=0.0f, float param5=0.0f, float param6=0.0f, float param7=0.0f)
Sends the command and calls the callback with the result.
Definition Vehicle.cc:2164
Vehicle * vehicle
Raw pointer is safe: CameraStruct is owned by QGCCameraManager which is a child of Vehicle.
QPointer< QGCCameraManager > manager
CameraStruct(QGCCameraManager *manager_, uint8_t compID_, Vehicle *vehicle_)
Callback info bundle for sendMavCommandWithHandler.
MavCmdResultHandler resultHandler
nullptr for no handler
RequestMessageResultHandlerFailureCode_t