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 // Stop the main heartbeat timer
114 _camerasLostHeartbeatTimer.stop();
115
116 qCDebug(CameraManagerLog) << this;
117}
118
120{
121 if ((sel != _currentCameraIndex) && (sel >= 0) && (sel < _cameras.count())) {
122 _currentCameraIndex = sel;
124 emit streamChanged();
125 }
126}
127
129{
130 qCDebug(CameraManagerLog) << ready;
131 if (!ready) {
132 return;
133 }
134 if (MultiVehicleManager::instance()->activeVehicle() != _vehicle) {
135 return;
136 }
137
138 _vehicleReadyState = true;
141}
142
144{
145 if (!_initialConnectComplete) {
146 return;
147 }
148
149 // Only pay attention to camera components (MAV_COMP_ID_CAMERA..CAMERA6)
150 // and camera-related messages proxied by the autopilot.
151 const bool fromAutopilot = message.compid == MAV_COMP_ID_AUTOPILOT1;
152 const bool fromCamera = (message.compid >= MAV_COMP_ID_CAMERA) && (message.compid <= MAV_COMP_ID_CAMERA6);
153 if ((message.sysid == _vehicle->id()) && (fromAutopilot || fromCamera)) {
154 switch (message.msgid) {
155 case MAVLINK_MSG_ID_CAMERA_CAPTURE_STATUS:
156 _handleCameraCaptureStatus(message);
157 break;
158 case MAVLINK_MSG_ID_STORAGE_INFORMATION:
159 _handleStorageInformation(message);
160 break;
161 case MAVLINK_MSG_ID_HEARTBEAT:
162 // Autopilot heartbeats should not be treated as camera discovery.
163 // Only actual camera component heartbeats should start CAMERA_INFORMATION requests.
164 if (fromCamera) {
165 _handleHeartbeat(message);
166 }
167 break;
168 case MAVLINK_MSG_ID_CAMERA_INFORMATION:
169 _handleCameraInfo(message);
170 break;
171 case MAVLINK_MSG_ID_CAMERA_SETTINGS:
172 _handleCameraSettings(message);
173 break;
174 case MAVLINK_MSG_ID_PARAM_EXT_ACK:
175 _handleParamExtAck(message);
176 break;
177 case MAVLINK_MSG_ID_PARAM_EXT_VALUE:
178 _handleParamExtValue(message);
179 break;
180 case MAVLINK_MSG_ID_VIDEO_STREAM_INFORMATION:
181 _handleVideoStreamInformation(message);
182 break;
183 case MAVLINK_MSG_ID_VIDEO_STREAM_STATUS:
184 _handleVideoStreamStatus(message);
185 break;
186 case MAVLINK_MSG_ID_BATTERY_STATUS:
187 _handleBatteryStatus(message);
188 break;
189 case MAVLINK_MSG_ID_CAMERA_TRACKING_IMAGE_STATUS:
190 _handleTrackingImageStatus(message);
191 break;
192 case MAVLINK_MSG_ID_CAMERA_FOV_STATUS:
193 _handleCameraFovStatus(message);
194 break;
195 default:
196 break;
197 }
198 }
199}
200
201void QGCCameraManager::_handleHeartbeat(const mavlink_message_t &message)
202{
203 const QString sCompID = QString::number(message.compid);
204
205 if (!_cameraInfoRequest.contains(sCompID)) {
206 qCDebug(CameraManagerLog) << "Heartbeat from" << QGCMAVLink::compIdToString(message.compid);
207 CameraStruct *pInfo = new CameraStruct(this, message.compid, _vehicle);
208 pInfo->lastHeartbeat.start();
209 _cameraInfoRequest[sCompID] = pInfo;
210 _requestCameraInfo(pInfo);
211 return;
212 }
213
214 CameraStruct *pInfo = _cameraInfoRequest[sCompID];
215 if (!pInfo) {
216 qCWarning(CameraManagerLog) << sCompID << "is null";
217 return;
218 }
219
220 if (pInfo->infoReceived) {
221 pInfo->lastHeartbeat.start();
222 return;
223 }
224
225 if (pInfo->lastHeartbeat.elapsed() > kSilentTimeoutMs) {
226 qCDebug(CameraManagerLog) << "Camera" << QGCMAVLink::compIdToString(message.compid) << "reappeared after being silent. Resetting retry count and requesting info.";
227 pInfo->retryCount = 0;
228 pInfo->backoffTimer.stop();
229 pInfo->lastHeartbeat.start();
230 _requestCameraInfo(pInfo);
231 return;
232 }
233
234 pInfo->lastHeartbeat.start();
235}
236
238{
239 if ((_currentCameraIndex < _cameras.count()) && !_cameras.isEmpty()) {
240 MavlinkCameraControlInterface *pCamera = qobject_cast<MavlinkCameraControlInterface*>(_cameras[_currentCameraIndex]);
241 return pCamera;
242 }
243 return nullptr;
244}
245
247{
249 if (pCamera) {
250 QGCVideoStreamInfo *pInfo = pCamera->currentStreamInstance();
251 return pInfo;
252 }
253 return nullptr;
254}
255
257{
259 if (pCamera) {
260 QGCVideoStreamInfo *pInfo = pCamera->thermalStreamInstance();
261 return pInfo;
262 }
263 return nullptr;
264}
265
266MavlinkCameraControlInterface *QGCCameraManager::_findCamera(int id)
267{
268 for (int i = 0; i < _cameras.count(); i++) {
269 if (!_cameras[i]) {
270 continue;
271 }
272 MavlinkCameraControlInterface *pCamera = qobject_cast<MavlinkCameraControlInterface*>(_cameras[i]);
273 if (!pCamera) {
274 qCCritical(CameraManagerLog) << "Invalid MavlinkCameraControlInterface instance";
275 continue;
276 }
277 if (pCamera->compID() == id) {
278 return pCamera;
279 }
280 }
281
282 // qCWarning(CameraManagerLog) << "Camera component id not found:" << id;
283 return nullptr;
284}
285
286void QGCCameraManager::_addCameraControlToLists(MavlinkCameraControlInterface *cameraControl)
287{
288 if (qobject_cast<SimulatedCameraControl*>(cameraControl)) {
289 qCDebug(CameraManagerLog) << "Adding simulated camera to list";
290 } else {
291 qCDebug(CameraManagerLog) << "Adding real camera to list - simulated camera will be removed if present";
292 }
293
294 _cameras.append(cameraControl);
295 _cameraLabels.append(cameraControl->modelName());
296 emit camerasChanged();
297 emit cameraLabelsChanged();
298
299 // If simulated camera is in list, remove it when a real camera appears
300 if ((_cameras.count() == 2) && (_cameras[0] == _simulatedCameraControl)) {
301 (void) _cameras.removeAt(0);
302 (void) _cameraLabels.removeAt(0);
303 emit camerasChanged();
304 emit cameraLabelsChanged();
306 }
307}
308
309void QGCCameraManager::_handleCameraInfo(const mavlink_message_t& message)
310{
311 const QString sCompID = QString::number(message.compid);
312 if (!_cameraInfoRequest.contains(sCompID)) {
313 qCDebug(CameraManagerLog) << "Ignoring - Camera info not requested for component" << QGCMAVLink::compIdToString(message.compid);
314 return;
315 }
316 if (_cameraInfoRequest[sCompID]->infoReceived) {
317 qCDebug(CameraManagerLog) << "Ignoring - Already received camera info for component" << QGCMAVLink::compIdToString(message.compid);
318 return;
319 }
320
322 mavlink_msg_camera_information_decode(&message, &info);
323 qCDebug(CameraManagerLog) << "Camera information received from" << QGCMAVLink::compIdToString(message.compid)
324 << "Model:" << reinterpret_cast<const char*>(info.model_name);
325 qCDebug(CameraManagerLog) << "Creating MavlinkCameraControlInterface for camera";
326
327 MavlinkCameraControlInterface *pCamera = _vehicle->firmwarePlugin()->createCameraControl(&info, _vehicle, message.compid, this);
328 if (pCamera) {
329 _addCameraControlToLists(pCamera);
330
331 _cameraInfoRequest[sCompID]->infoReceived = true;
332 _cameraInfoRequest[sCompID]->retryCount = 0;
333 _cameraInfoRequest[sCompID]->backoffTimer.stop();
334 qCDebug(CameraManagerLog) << "Success for compId" << QGCMAVLink::compIdToString(message.compid) << "- reset retry counter";
335 }
336
337 double aspect = std::numeric_limits<double>::quiet_NaN();
338
339 if (info.resolution_h > 0 && info.resolution_v > 0) {
340 aspect = double(info.resolution_v) / double(info.resolution_h);
341 } else if (info.sensor_size_h > 0.f && info.sensor_size_v > 0.f) {
342 aspect = double(info.sensor_size_v) / double(info.sensor_size_h);
343 }
344
345 _aspectByCompId.insert(message.compid, aspect);
346}
347
349{
350 QList<QString> stale;
351 for (auto it = _cameraInfoRequest.cbegin(), end = _cameraInfoRequest.cend(); it != end; ++it) {
352 const auto *info = it.value();
353 if (info && info->infoReceived && (info->lastHeartbeat.elapsed() > kSilentTimeoutMs)) {
354 stale.push_back(it.key());
355 }
356 }
357 if (stale.isEmpty()) {
358 return;
359 }
360
361 bool removedAny = false;
362 for (const QString& key : std::as_const(stale)) {
363 CameraStruct* pInfo = _cameraInfoRequest.take(key);
364 if (!pInfo) {
365 continue;
366 }
367
368 MavlinkCameraControlInterface* pCamera = _findCamera(pInfo->compID);
369 if (pCamera) {
370 const int idx = _cameras.indexOf(pCamera);
371 if (idx >= 0) {
372 qCDebug(CameraManagerLog) << "Removing lost camera" << QGCMAVLink::compIdToString(pInfo->compID);
373 removedAny = true;
374 (void) _cameraLabels.removeAt(idx);
375 (void) _cameras.removeAt(idx);
376 pCamera->deleteLater();
377 }
378 }
379
380 delete pInfo;
381 }
382
383 if (!removedAny) {
384 return;
385 }
386
387 if (_cameras.isEmpty()) {
388 _addCameraControlToLists(_simulatedCameraControl);
389 }
390
391 emit cameraLabelsChanged();
392 emit camerasChanged();
393
394 if (_currentCameraIndex != 0) {
395 _currentCameraIndex = 0;
397 }
398 emit streamChanged();
399}
400
401void QGCCameraManager::_handleCameraCaptureStatus(const mavlink_message_t &message)
402{
403 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
404 if (pCamera) {
405 mavlink_camera_capture_status_t cap{};
406 mavlink_msg_camera_capture_status_decode(&message, &cap);
407 pCamera->handleCameraCaptureStatus(cap);
408 }
409}
410
411void QGCCameraManager::_handleStorageInformation(const mavlink_message_t &message)
412{
413 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
414 if (pCamera) {
415 mavlink_storage_information_t st{};
416 mavlink_msg_storage_information_decode(&message, &st);
417 pCamera->handleStorageInformation(st);
418 }
419}
420
421void QGCCameraManager::_handleCameraSettings(const mavlink_message_t& message)
422{
423 auto pCamera = _findCamera(message.compid);
424 if (pCamera) {
425 mavlink_camera_settings_t settings{};
426 mavlink_msg_camera_settings_decode(&message, &settings);
427 pCamera->handleCameraSettings(settings);
428
429 const int newZoom = static_cast<int>(settings.zoomLevel);
430 if (QThread::currentThread() == thread()) {
431 _setCurrentZoomLevel(newZoom);
432 } else {
433 QMetaObject::invokeMethod(
434 this,
435 "_setCurrentZoomLevel",
436 Qt::QueuedConnection,
437 Q_ARG(int, newZoom)
438 );
439 }
440
441 requestCameraFovForComp(message.compid);
442 }
443}
444
445void QGCCameraManager::_handleParamExtAck(const mavlink_message_t &message)
446{
447 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
448 if (pCamera) {
449 mavlink_param_ext_ack_t ack{};
450 mavlink_msg_param_ext_ack_decode(&message, &ack);
451 pCamera->handleParamExtAck(ack);
452 }
453}
454
455void QGCCameraManager::_handleParamExtValue(const mavlink_message_t &message)
456{
457 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
458 if (pCamera) {
459 mavlink_param_ext_value_t value{};
460 mavlink_msg_param_ext_value_decode(&message, &value);
461 pCamera->handleParamExtValue(value);
462 }
463}
464
465void QGCCameraManager::_handleVideoStreamInformation(const mavlink_message_t &message)
466{
467 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
468 if (pCamera) {
469 mavlink_video_stream_information_t streamInfo{};
470 mavlink_msg_video_stream_information_decode(&message, &streamInfo);
471 pCamera->handleVideoStreamInformation(streamInfo);
472 emit streamChanged();
473 }
474}
475
476void QGCCameraManager::_handleVideoStreamStatus(const mavlink_message_t &message)
477{
478 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
479 if (pCamera) {
480 mavlink_video_stream_status_t streamStatus{};
481 mavlink_msg_video_stream_status_decode(&message, &streamStatus);
482 pCamera->handleVideoStreamStatus(streamStatus);
483 }
484}
485
486void QGCCameraManager::_handleBatteryStatus(const mavlink_message_t &message)
487{
488 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
489 if (pCamera) {
490 mavlink_battery_status_t batteryStatus{};
491 mavlink_msg_battery_status_decode(&message, &batteryStatus);
492 pCamera->handleBatteryStatus(batteryStatus);
493 }
494}
495
496void QGCCameraManager::_handleTrackingImageStatus(const mavlink_message_t &message)
497{
498 MavlinkCameraControlInterface *pCamera = _findCamera(message.compid);
499 if (pCamera) {
500 mavlink_camera_tracking_image_status_t tis{};
501 mavlink_msg_camera_tracking_image_status_decode(&message, &tis);
502 pCamera->handleTrackingImageStatus(tis);
503 }
504}
505
507
508static void _requestCameraInfoCommandResultHandler(void *resultHandlerData, int /*compId*/, const mavlink_command_ack_t &ack, Vehicle::MavCmdResultFailureCode_t failureCode)
509{
510 auto *cameraInfo = static_cast<QGCCameraManager::CameraStruct*>(resultHandlerData);
511
512 if (ack.result != MAV_RESULT_ACCEPTED) {
513 qCDebug(CameraManagerLog) << "MAV_CMD_REQUEST_CAMERA_INFORMATION failed. compId" << QGCMAVLink::compIdToString(cameraInfo->compID)
514 << "Result:" << QGCMAVLink::mavResultToString(ack.result)
515 << "FailureCode:" << Vehicle::mavCmdResultFailureCodeToString(failureCode)
516 << "retryCount:" << cameraInfo->retryCount;
517 _handleCameraInfoRetry(cameraInfo);
518 }
519}
520
521static void _requestCameraInfoMessageResultHandler(void *resultHandlerData, MAV_RESULT result, Vehicle::RequestMessageResultHandlerFailureCode_t failureCode, [[maybe_unused]] const mavlink_message_t &message)
522{
523 auto *cameraInfo = static_cast<QGCCameraManager::CameraStruct*>(resultHandlerData);
524
525 if (result != MAV_RESULT_ACCEPTED) {
526 qCDebug(CameraManagerLog) << "MAV_CMD_REQUEST_MESSAGE:MAVLINK_MSG_ID_CAMERA_INFORMATION failed. compId" << QGCMAVLink::compIdToString(cameraInfo->compID)
527 << "Result:" << QGCMAVLink::mavResultToString(result)
528 << "FailureCode:" << Vehicle::requestMessageResultHandlerFailureCodeToString(failureCode)
529 << "retryCount:" << cameraInfo->retryCount;
530 _handleCameraInfoRetry(cameraInfo);
531 }
532}
533
535{
536 // Give up after max attempts
537 if (pInfo->retryCount >= kMaxRetryCount) {
538 qCDebug(CameraManagerLog) << "Giving up requesting camera info after" << pInfo->retryCount << "attempts for compId" << QGCMAVLink::compIdToString(pInfo->compID);
539 return;
540 }
541
542 // Alternate between REQUEST_MESSAGE and REQUEST_CAMERA_INFORMATION
543 if ((pInfo->retryCount % 2) == 0) {
544 qCDebug(CameraManagerLog) << "Using MAV_CMD_REQUEST_MESSAGE:CAMERA_INFORMATION for compId" << QGCMAVLink::compIdToString(pInfo->compID);
545 manager->vehicle()->requestMessage(_requestCameraInfoMessageResultHandler, pInfo, pInfo->compID, MAVLINK_MSG_ID_CAMERA_INFORMATION);
546 } else {
547 qCDebug(CameraManagerLog) << "Using MAV_CMD_REQUEST_CAMERA_INFORMATION for compId" << QGCMAVLink::compIdToString(pInfo->compID);
548
549 Vehicle::MavCmdAckHandlerInfo_t ackHandlerInfo{};
551 ackHandlerInfo.resultHandlerData = pInfo;
552 ackHandlerInfo.progressHandler = nullptr;
553 ackHandlerInfo.progressHandlerData = nullptr;
554
555 pInfo->vehicle->sendMavCommandWithHandler(&ackHandlerInfo, pInfo->compID, MAV_CMD_REQUEST_CAMERA_INFORMATION, 1 /* request camera capabilities */);
556 }
557}
558
560{
561 if (!cameraInfo) {
562 return;
563 }
564
565 QGCCameraManager *manager = cameraInfo->manager;
566 if (!manager) {
567 qCDebug(CameraManagerLog) << "manager is unavailable for compId" << QGCMAVLink::compIdToString(cameraInfo->compID);
568 return;
569 }
570
571 cameraInfo->retryCount++;
572
573 // For even attempts >= 2, use exponential backoff
574 if ((cameraInfo->retryCount >= 2) && ((cameraInfo->retryCount % 2) == 0)) {
575 const int delaySeconds = 1 << (cameraInfo->retryCount / 2);
576 const int delayMs = delaySeconds * 1000;
577
578 qCDebug(CameraManagerLog) << "Waiting" << delaySeconds << "seconds before retry for compId" << QGCMAVLink::compIdToString(cameraInfo->compID);
579
580 cameraInfo->backoffTimer.stop();
581 (void) QObject::disconnect(&cameraInfo->backoffTimer, nullptr, nullptr, nullptr);
582
583 // Capture compID by value and look up the struct
584 const uint8_t compId = cameraInfo->compID;
585 QPointer<QGCCameraManager> mgrGuard(manager);
586
587 (void) QObject::connect(&cameraInfo->backoffTimer, &QTimer::timeout,
588 &cameraInfo->backoffTimer, // context ensures timer is still alive
589 [mgrGuard, compId]() {
590 if (!mgrGuard) {
591 return;
592 }
593
594 auto* info = mgrGuard->findCameraStruct(compId);
595 if (info) {
596 _requestCameraInfoHelper(mgrGuard.data(), info);
597 }
598 });
599
600 cameraInfo->backoffTimer.start(delayMs);
601 } else {
602 _requestCameraInfoHelper(manager, cameraInfo);
603 }
604}
605
606void QGCCameraManager::_requestCameraInfo(CameraStruct *pInfo)
607{
608 if (!pInfo) {
609 return;
610 }
611 _requestCameraInfoHelper(this, pInfo);
612}
613
615{
616 qCDebug(CameraManagerLog) << "Joystick changed";
617 if (_activeJoystick) {
618 (void) disconnect(_activeJoystick, &Joystick::stepZoom, this, &QGCCameraManager::_stepZoom);
619 (void) disconnect(_activeJoystick, &Joystick::startContinuousZoom, this, &QGCCameraManager::_startZoom);
620 (void) disconnect(_activeJoystick, &Joystick::stopContinuousZoom, this, &QGCCameraManager::_stopZoom);
621 (void) disconnect(_activeJoystick, &Joystick::stepFocus, this, &QGCCameraManager::_stepFocus);
622 (void) disconnect(_activeJoystick, &Joystick::startContinuousFocus, this, &QGCCameraManager::_startFocus);
623 (void) disconnect(_activeJoystick, &Joystick::stopContinuousFocus, this, &QGCCameraManager::_stopFocus);
624 (void) disconnect(_activeJoystick, &Joystick::stepCamera, this, &QGCCameraManager::_stepCamera);
625 (void) disconnect(_activeJoystick, &Joystick::stepStream, this, &QGCCameraManager::_stepStream);
626 (void) disconnect(_activeJoystick, &Joystick::triggerCamera, this, &QGCCameraManager::_triggerCamera);
627 (void) disconnect(_activeJoystick, &Joystick::startVideoRecord, this, &QGCCameraManager::_startVideoRecording);
628 (void) disconnect(_activeJoystick, &Joystick::stopVideoRecord, this, &QGCCameraManager::_stopVideoRecording);
629 (void) disconnect(_activeJoystick, &Joystick::toggleVideoRecord, this, &QGCCameraManager::_toggleVideoRecording);
630 }
631
632 _activeJoystick = joystick;
633
634 if (_activeJoystick) {
635 (void) connect(_activeJoystick, &Joystick::stepZoom, this, &QGCCameraManager::_stepZoom, Qt::UniqueConnection);
636 (void) connect(_activeJoystick, &Joystick::startContinuousZoom, this, &QGCCameraManager::_startZoom, Qt::UniqueConnection);
637 (void) connect(_activeJoystick, &Joystick::stopContinuousZoom, this, &QGCCameraManager::_stopZoom, Qt::UniqueConnection);
638 (void) connect(_activeJoystick, &Joystick::stepFocus, this, &QGCCameraManager::_stepFocus, Qt::UniqueConnection);
639 (void) connect(_activeJoystick, &Joystick::startContinuousFocus, this, &QGCCameraManager::_startFocus, Qt::UniqueConnection);
640 (void) connect(_activeJoystick, &Joystick::stopContinuousFocus, this, &QGCCameraManager::_stopFocus, Qt::UniqueConnection);
641 (void) connect(_activeJoystick, &Joystick::stepCamera, this, &QGCCameraManager::_stepCamera, Qt::UniqueConnection);
642 (void) connect(_activeJoystick, &Joystick::stepStream, this, &QGCCameraManager::_stepStream, Qt::UniqueConnection);
643 (void) connect(_activeJoystick, &Joystick::triggerCamera, this, &QGCCameraManager::_triggerCamera, Qt::UniqueConnection);
644 (void) connect(_activeJoystick, &Joystick::startVideoRecord, this, &QGCCameraManager::_startVideoRecording, Qt::UniqueConnection);
645 (void) connect(_activeJoystick, &Joystick::stopVideoRecord, this, &QGCCameraManager::_stopVideoRecording, Qt::UniqueConnection);
646 (void) connect(_activeJoystick, &Joystick::toggleVideoRecord, this, &QGCCameraManager::_toggleVideoRecording, Qt::UniqueConnection);
647 }
648}
649
651{
653 if (pCamera) {
654 pCamera->takePhoto();
655 }
656}
657
659{
661 if (pCamera) {
662 pCamera->startVideoRecording();
663 }
664}
665
667{
669 if (pCamera) {
670 pCamera->stopVideoRecording();
671 }
672}
673
675{
677 if (pCamera) {
678 pCamera->toggleVideoRecording();
679 }
680}
681
683{
684 if (_lastZoomChange.elapsed() > 40) {
685 _lastZoomChange.start();
686 qCDebug(CameraManagerLog) << "Step Camera Zoom" << direction;
688 if (pCamera) {
689 pCamera->stepZoom(direction);
690 }
691 }
692}
693
695{
696 qCDebug(CameraManagerLog) << "Start Camera Zoom" << direction;
698 if (pCamera) {
699 pCamera->startZoom(direction);
700 }
701}
702
704{
705 qCDebug(CameraManagerLog) << "Stop Camera Zoom";
707 if (pCamera) {
708 pCamera->stopZoom();
709 }
710}
711
713{
714 if (_lastFocusChange.elapsed() > 40) {
715 _lastFocusChange.start();
716 qCDebug(CameraManagerLog) << "Step Camera Focus" << direction;
718 if (pCamera) {
719 pCamera->stepFocus(direction);
720 }
721 }
722}
723
725{
726 qCDebug(CameraManagerLog) << "Start Camera Focus" << direction;
728 if (pCamera) {
729 pCamera->startFocus(direction);
730 }
731}
732
734{
735 qCDebug(CameraManagerLog) << "Stop Camera Focus";
737 if (pCamera) {
738 pCamera->stopFocus();
739 }
740}
741
743{
744 if (_lastCameraChange.elapsed() > 1000) {
745 _lastCameraChange.start();
746 qCDebug(CameraManagerLog) << "Step Camera" << direction;
747 int camera = _currentCameraIndex + direction;
748 if (camera < 0) {
749 camera = _cameras.count() - 1;
750 } else if (camera >= _cameras.count()) {
751 camera = 0;
752 }
753 setCurrentCamera(camera);
754 }
755}
756
758{
759 if (_lastCameraChange.elapsed() > 1000) {
760 _lastCameraChange.start();
762 if (pCamera) {
763 qCDebug(CameraManagerLog) << "Step Camera Stream" << direction;
764 int stream = pCamera->currentStream() + direction;
765 if (stream < 0) {
766 stream = pCamera->streams()->count() - 1;
767 } else if (stream >= pCamera->streams()->count()) {
768 stream = 0;
769 }
770 pCamera->setCurrentStream(stream);
771 }
772 }
773}
774
775const QVariantList &QGCCameraManager::cameraList() const
776{
777 if (_cameraList.isEmpty()) {
778 const QList<CameraMetaData*> cams = CameraMetaData::parseCameraMetaData();
779 _cameraList.reserve(cams.size());
780
781 for (CameraMetaData *cam : cams) {
782 _cameraList << QVariant::fromValue(cam);
783 }
784 }
785 return _cameraList;
786}
787
789 if (!_vehicle) {
790 qCWarning(CameraManagerLog) << "requestCameraFovForComp: vehicle is null";
791 return;
792 }
793 _vehicle->requestMessage(_requestFovOnZoom_Handler, /*user*/this,
794 compId, MAVLINK_MSG_ID_CAMERA_FOV_STATUS);
795}
796
797//-----------------------------------------------------------------------------
798double QGCCameraManager::aspectForComp(int compId) const {
799 auto it = _aspectByCompId.constFind(compId);
800 return (it == _aspectByCompId.cend())
801 ? std::numeric_limits<double>::quiet_NaN()
802 : it.value();
803}
804
806 if (auto* cam = currentCameraInstance()) {
807 return aspectForComp(cam->compID());
808 }
809 return std::numeric_limits<double>::quiet_NaN();
810}
811void QGCCameraManager::_handleCameraFovStatus(const mavlink_message_t& message)
812{
813 mavlink_camera_fov_status_t fov{};
814 mavlink_msg_camera_fov_status_decode(&message, &fov);
815
816 if (!std::isfinite(fov.hfov) || fov.hfov <= 0.0 || fov.hfov >= 180.0) {
817 return;
818 }
819
820 double aspect = aspectForComp(message.compid);
821 if (!std::isfinite(aspect) || aspect <= 0.0) {
822 aspect = 16.0 / 9.0;
823 }
824
825 const double hfovRad = fov.hfov * kPi / 180.0;
826 const double vfovRad = 2.0 * std::atan(std::tan(hfovRad * 0.5) * aspect);
827 const double vfovDeg = vfovRad * 180.0 / kPi;
828
829 if (!std::isfinite(vfovDeg) || vfovDeg <= 0.0 || vfovDeg >= 180.0) {
830 qCWarning(CameraManagerLog) << "Invalid calculated VFOV:" << vfovDeg
831 << "hfov:" << fov.hfov
832 << "aspect:" << aspect
833 << "compId:" << message.compid;
834 return;
835 }
836
838 settings->cameraHFov()->setRawValue(fov.hfov);
839 settings->cameraVFov()->setRawValue(vfovDeg);
840}
841
842void QGCCameraManager::_setCurrentZoomLevel(int level)
843{
844 if (_zoomValueCurrent == level) {
845 return;
846 }
847 _zoomValueCurrent = level;
849}
850
852{
853 return _zoomValueCurrent;
854}
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 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()
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:3350
void initialConnectComplete()
FirmwarePlugin * firmwarePlugin()
Provides access to the Firmware Plugin for this Vehicle.
Definition Vehicle.h:444
int id() const
Definition Vehicle.h:425
void mavlinkMessageReceived(const mavlink_message_t &message)
static QString requestMessageResultHandlerFailureCodeToString(RequestMessageResultHandlerFailureCode_t failureCode)
Definition Vehicle.cc:3345
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:2252
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:2170
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