QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
MockLinkCamera.cc
Go to the documentation of this file.
1#include "MockLinkCamera.h"
2#include "MockLink.h"
5
6#include <QtCore/QDateTime>
7#include <QtCore/QLoggingCategory>
8
9QGC_LOGGING_CATEGORY(MockLinkCameraLog, "Comms.MockLink.MockLinkCamera")
10
12 bool captureVideo,
13 bool captureImage,
14 bool hasModes,
15 bool hasVideoStream,
16 bool canCaptureImageInVideoMode,
17 bool canCaptureVideoInImageMode,
18 bool hasBasicZoom,
19 bool hasTrackingPoint,
20 bool hasTrackingRectangle)
21 : _mockLink(mockLink)
22{
23 // Build capability flags from configuration
24 uint32_t configuredFlags = 0;
25 if (captureVideo) configuredFlags |= CAMERA_CAP_FLAGS_CAPTURE_VIDEO;
26 if (captureImage) configuredFlags |= CAMERA_CAP_FLAGS_CAPTURE_IMAGE;
27 if (hasModes) configuredFlags |= CAMERA_CAP_FLAGS_HAS_MODES;
28 if (hasVideoStream) configuredFlags |= CAMERA_CAP_FLAGS_HAS_VIDEO_STREAM;
29 if (canCaptureImageInVideoMode) configuredFlags |= CAMERA_CAP_FLAGS_CAN_CAPTURE_IMAGE_IN_VIDEO_MODE;
30 if (canCaptureVideoInImageMode) configuredFlags |= CAMERA_CAP_FLAGS_CAN_CAPTURE_VIDEO_IN_IMAGE_MODE;
31 if (hasBasicZoom) configuredFlags |= CAMERA_CAP_FLAGS_HAS_BASIC_ZOOM;
32 if (hasTrackingPoint) configuredFlags |= CAMERA_CAP_FLAGS_HAS_TRACKING_POINT;
33 if (hasTrackingRectangle) configuredFlags |= CAMERA_CAP_FLAGS_HAS_TRACKING_RECTANGLE;
34
35 // Camera 1: full-featured with configurable flags
36 _cameras[0].compId = MAV_COMP_ID_CAMERA;
37 _cameras[0].capFlags = configuredFlags;
38 _cameras[0].cameraMode = CAMERA_MODE_IMAGE;
39 if ((configuredFlags & CAMERA_CAP_FLAGS_HAS_VIDEO_STREAM) && (configuredFlags & CAMERA_CAP_FLAGS_HAS_MODES)) {
40 _cameras[0].cameraMode = CAMERA_MODE_VIDEO;
41 }
42
43 // Camera 2: photo-only (always CAPTURE_IMAGE only)
44 _cameras[1].compId = MAV_COMP_ID_CAMERA2;
45 _cameras[1].capFlags = CAMERA_CAP_FLAGS_CAPTURE_IMAGE;
46 _cameras[1].cameraMode = CAMERA_MODE_IMAGE;
47}
48
49MockLinkCamera::CameraState *MockLinkCamera::_findCamera(uint8_t compId)
50{
51 for (uint8_t i = 0; i < kNumCameras; i++) {
52 if (_cameras[i].compId == compId) {
53 return &_cameras[i];
54 }
55 }
56 return nullptr;
57}
58
59const char *MockLinkCamera::_imageCaptureStatusToString(uint8_t status)
60{
61 switch (status) {
62 case ImageCaptureIdle: return "Idle";
63 case ImageCaptureInProgress: return "InProgress";
64 case ImageCaptureInterval: return "Interval";
65 case ImageCaptureIntervalCapture: return "IntervalCapture";
66 default: return "Unknown";
67 }
68}
69
71{
72 for (uint8_t i = 0; i < kNumCameras; i++) {
74 (void) mavlink_msg_heartbeat_pack_chan(
75 _mockLink->vehicleId(),
76 _cameras[i].compId,
77 _mockLink->mavlinkChannel(),
78 &msg,
79 MAV_TYPE_CAMERA,
80 MAV_AUTOPILOT_INVALID,
81 0,
82 0,
83 MAV_STATE_ACTIVE);
84 _mockLink->respondWithMavlinkMessage(msg);
85 }
86}
87
89{
90 // Runs every 100ms (10Hz on worker thread). Reads and modifies camera state that main thread
91 // also modifies via command handlers. Protect entire state check-and-update to maintain consistency.
92 QMutexLocker locker(&_camerasMutex);
93 const qint64 now = QDateTime::currentMSecsSinceEpoch();
94
95 for (uint8_t i = 0; i < kNumCameras; i++) {
96 CameraState *cam = &_cameras[i];
97
98 // Check for single-shot capture completion (after 500ms)
99 if (cam->singleShotStartMs > 0 && (now - cam->singleShotStartMs) >= 500) {
100 cam->imagesCaptured++;
102 cam->singleShotStartMs = 0;
103
104 qCDebug(MockLinkCameraLog) << "Camera" << cam->compId << "single-shot complete, total:" << cam->imagesCaptured;
105 _sendCameraImageCaptured(cam->compId);
106 _sendCameraCaptureStatus(cam->compId);
107 }
108 }
109}
110
112{
113 if (msg.msgid != MAVLINK_MSG_ID_COMMAND_LONG) {
114 return false;
115 }
116
117 mavlink_command_long_t request{};
118 mavlink_msg_command_long_decode(&msg, &request);
119
120 // Check if this command targets a camera component
121 if (request.target_component < MAV_COMP_ID_CAMERA || request.target_component > MAV_COMP_ID_CAMERA6) {
122 return false;
123 }
124
125 const uint8_t targetCompId = request.target_component;
126
127 if (request.command == MAV_CMD_REQUEST_MESSAGE) {
128 return _handleRequestMessage(request, targetCompId);
129 }
130
131 return _handleCameraCommand(request, targetCompId);
132}
133
134bool MockLinkCamera::_handleCameraCommand(const mavlink_command_long_t &request, uint8_t targetCompId)
135{
136 // Thread-safe access: Main thread modifying camera state that worker thread reads every 100ms.
137 // Serialize all camera state modifications to avoid worker seeing inconsistent state
138 // (e.g., trying to complete capture while main thread is starting a new one).
139 QMutexLocker locker(&_camerasMutex);
140 CameraState *cam = _findCamera(targetCompId);
141 if (!cam) {
142 return false;
143 }
144
145 switch (request.command) {
146 case MAV_CMD_REQUEST_CAMERA_INFORMATION:
147 _sendCameraInformation(targetCompId);
148 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
149 return true;
150
151 case MAV_CMD_REQUEST_CAMERA_SETTINGS:
152 _sendCameraSettings(targetCompId);
153 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
154 return true;
155
156 case MAV_CMD_REQUEST_STORAGE_INFORMATION:
157 _sendStorageInformation(targetCompId);
158 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
159 return true;
160
161 case MAV_CMD_REQUEST_CAMERA_CAPTURE_STATUS:
162 _sendCameraCaptureStatus(targetCompId);
163 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
164 return true;
165
166 case MAV_CMD_REQUEST_VIDEO_STREAM_INFORMATION:
167 if (cam->capFlags & CAMERA_CAP_FLAGS_HAS_VIDEO_STREAM) {
168 const uint8_t streamId = static_cast<uint8_t>(request.param1);
169 if (streamId == 0) {
170 // Request all streams
171 for (uint8_t s = 1; s <= kNumStreams; s++) {
172 _sendVideoStreamInformation(targetCompId, s);
173 }
174 } else {
175 _sendVideoStreamInformation(targetCompId, streamId);
176 }
177 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
178 } else {
179 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
180 }
181 return true;
182
183 case MAV_CMD_REQUEST_VIDEO_STREAM_STATUS:
184 if (cam->capFlags & CAMERA_CAP_FLAGS_HAS_VIDEO_STREAM) {
185 const uint8_t streamId = static_cast<uint8_t>(request.param1);
186 if (streamId == 0) {
187 for (uint8_t s = 1; s <= kNumStreams; s++) {
188 _sendVideoStreamStatus(targetCompId, s);
189 }
190 } else {
191 _sendVideoStreamStatus(targetCompId, streamId);
192 }
193 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
194 } else {
195 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
196 }
197 return true;
198
199 case MAV_CMD_SET_CAMERA_MODE:
200 if (cam->capFlags & CAMERA_CAP_FLAGS_HAS_MODES) {
201 const uint8_t requestedMode = static_cast<uint8_t>(request.param2);
202
203 if ((requestedMode != CAMERA_MODE_IMAGE) && (requestedMode != CAMERA_MODE_VIDEO)) {
204 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
205 return true;
206 }
207
208 const bool supportsImageMode =
209 (cam->capFlags & CAMERA_CAP_FLAGS_CAPTURE_IMAGE) ||
210 (cam->capFlags & CAMERA_CAP_FLAGS_HAS_VIDEO_STREAM);
211 const bool supportsVideoMode =
212 (cam->capFlags & CAMERA_CAP_FLAGS_CAPTURE_VIDEO) ||
213 (cam->capFlags & CAMERA_CAP_FLAGS_HAS_VIDEO_STREAM);
214
215 if ((requestedMode == CAMERA_MODE_IMAGE && !supportsImageMode) ||
216 (requestedMode == CAMERA_MODE_VIDEO && !supportsVideoMode)) {
217 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
218 return true;
219 }
220
221 cam->cameraMode = requestedMode;
222 qCDebug(MockLinkCameraLog) << "Camera" << targetCompId << "mode set to" << cam->cameraMode;
223 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
224 // Send updated settings after mode change
225 _sendCameraSettings(targetCompId);
226 } else {
227 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
228 }
229 return true;
230
231 case MAV_CMD_IMAGE_START_CAPTURE:
232 if (cam->capFlags & CAMERA_CAP_FLAGS_CAPTURE_IMAGE) {
233 if ((cam->capFlags & CAMERA_CAP_FLAGS_HAS_MODES) &&
234 (cam->cameraMode == CAMERA_MODE_VIDEO) &&
235 !(cam->capFlags & CAMERA_CAP_FLAGS_CAN_CAPTURE_IMAGE_IN_VIDEO_MODE)) {
236 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
237 return true;
238 }
239
240 const float interval = request.param2;
241 const int count = static_cast<int>(request.param3);
242
243 // Set capture status based on interval
244 if (interval > 0) {
245 // Interval capture mode
246 cam->image_status = ImageCaptureIntervalCapture;
247 cam->image_interval = interval;
248 cam->imagesCaptured += (count > 0) ? count : 1;
249 cam->singleShotStartMs = 0;
250 } else {
251 // Single shot - start capture, count will increment after 0.5s
252 cam->image_status = ImageCaptureInProgress;
253 cam->image_interval = 0.0f;
254 cam->singleShotStartMs = QDateTime::currentMSecsSinceEpoch();
255 }
256
257 qCDebug(MockLinkCameraLog) << "Camera" << targetCompId << "image capture started"
258 << "interval:" << interval << "count:" << count;
259 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
260 // Send capture status update
261 _sendCameraCaptureStatus(targetCompId);
262 } else {
263 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
264 }
265 return true;
266
267 case MAV_CMD_IMAGE_STOP_CAPTURE:
268 if (cam->capFlags & CAMERA_CAP_FLAGS_CAPTURE_IMAGE) {
269 cam->image_status = ImageCaptureIdle;
270 cam->image_interval = 0.0f;
271 cam->singleShotStartMs = 0;
272 qCDebug(MockLinkCameraLog) << "Camera" << targetCompId << "image capture stopped";
273 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
274 _sendCameraCaptureStatus(targetCompId);
275 } else {
276 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
277 }
278 return true;
279
280 case MAV_CMD_VIDEO_START_CAPTURE:
281 if (cam->capFlags & CAMERA_CAP_FLAGS_CAPTURE_VIDEO) {
282 if ((cam->capFlags & CAMERA_CAP_FLAGS_HAS_MODES) &&
283 (cam->cameraMode == CAMERA_MODE_IMAGE) &&
284 !(cam->capFlags & CAMERA_CAP_FLAGS_CAN_CAPTURE_VIDEO_IN_IMAGE_MODE)) {
285 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
286 return true;
287 }
288
289 cam->recording = true;
290 qCDebug(MockLinkCameraLog) << "Camera" << targetCompId << "video recording started";
291 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
292 _sendCameraCaptureStatus(targetCompId);
293 } else {
294 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
295 }
296 return true;
297
298 case MAV_CMD_VIDEO_STOP_CAPTURE:
299 if (cam->capFlags & CAMERA_CAP_FLAGS_CAPTURE_VIDEO) {
300 cam->recording = false;
301 qCDebug(MockLinkCameraLog) << "Camera" << targetCompId << "video recording stopped";
302 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
303 _sendCameraCaptureStatus(targetCompId);
304 } else {
305 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
306 }
307 return true;
308
309 case MAV_CMD_STORAGE_FORMAT:
310 qCDebug(MockLinkCameraLog) << "Camera" << targetCompId << "storage formatted";
311 cam->imagesCaptured = 0;
312 cam->image_status = ImageCaptureIdle;
313 cam->image_interval = 0.0f;
314 cam->singleShotStartMs = 0;
315 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
316 _sendStorageInformation(targetCompId);
317 return true;
318
319 case MAV_CMD_SET_CAMERA_ZOOM:
320 if (cam->capFlags & CAMERA_CAP_FLAGS_HAS_BASIC_ZOOM) {
321 cam->zoomLevel = request.param2;
322 qCDebug(MockLinkCameraLog) << "Camera" << targetCompId << "zoom set to" << cam->zoomLevel;
323 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
324 _sendCameraSettings(targetCompId);
325 } else {
326 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
327 }
328 return true;
329
330 case MAV_CMD_SET_CAMERA_FOCUS:
331 _sendCommandAck(targetCompId, request.command, MAV_RESULT_DENIED);
332 return true;
333
334 case MAV_CMD_RESET_CAMERA_SETTINGS:
335 cam->cameraMode = CAMERA_MODE_IMAGE;
336 cam->zoomLevel = 1.0f;
337 cam->focusLevel = 0.0f;
338 cam->image_status = ImageCaptureIdle;
339 cam->image_interval = 0.0f;
340 cam->singleShotStartMs = 0;
341 qCDebug(MockLinkCameraLog) << "Camera" << targetCompId << "settings reset";
342 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
343 _sendCameraSettings(targetCompId);
344 return true;
345
346 case MAV_CMD_CAMERA_TRACK_POINT:
347 case MAV_CMD_CAMERA_TRACK_RECTANGLE:
348 case MAV_CMD_CAMERA_STOP_TRACKING:
349 qCDebug(MockLinkCameraLog) << "Camera" << targetCompId << "tracking command" << request.command;
350 _sendCommandAck(targetCompId, request.command, MAV_RESULT_ACCEPTED);
351 return true;
352
353 default:
354 break;
355 }
356
357 return false;
358}
359
360bool MockLinkCamera::_handleRequestMessage(const mavlink_command_long_t &request, uint8_t targetCompId)
361{
362 const CameraState *cam = _findCamera(targetCompId);
363 if (!cam) {
364 return false;
365 }
366
367 const int msgId = static_cast<int>(request.param1);
368
369 switch (msgId) {
370 case MAVLINK_MSG_ID_CAMERA_INFORMATION:
371 _sendCameraInformation(targetCompId);
372 _sendCommandAck(targetCompId, MAV_CMD_REQUEST_MESSAGE, MAV_RESULT_ACCEPTED, msgId);
373 return true;
374
375 case MAVLINK_MSG_ID_CAMERA_SETTINGS:
376 _sendCameraSettings(targetCompId);
377 _sendCommandAck(targetCompId, MAV_CMD_REQUEST_MESSAGE, MAV_RESULT_ACCEPTED, msgId);
378 return true;
379
380 case MAVLINK_MSG_ID_STORAGE_INFORMATION:
381 _sendStorageInformation(targetCompId);
382 _sendCommandAck(targetCompId, MAV_CMD_REQUEST_MESSAGE, MAV_RESULT_ACCEPTED, msgId);
383 return true;
384
385 case MAVLINK_MSG_ID_CAMERA_CAPTURE_STATUS:
386 _sendCameraCaptureStatus(targetCompId);
387 _sendCommandAck(targetCompId, MAV_CMD_REQUEST_MESSAGE, MAV_RESULT_ACCEPTED, msgId);
388 return true;
389
390 case MAVLINK_MSG_ID_VIDEO_STREAM_INFORMATION:
391 {
392 if (!(cam->capFlags & CAMERA_CAP_FLAGS_HAS_VIDEO_STREAM)) {
393 _sendCommandAck(targetCompId, MAV_CMD_REQUEST_MESSAGE, MAV_RESULT_DENIED, msgId);
394 return true;
395 }
396 const uint8_t streamId = static_cast<uint8_t>(request.param2);
397 if (streamId == 0) {
398 for (uint8_t s = 1; s <= kNumStreams; s++) {
399 _sendVideoStreamInformation(targetCompId, s);
400 }
401 } else {
402 _sendVideoStreamInformation(targetCompId, streamId);
403 }
404 _sendCommandAck(targetCompId, MAV_CMD_REQUEST_MESSAGE, MAV_RESULT_ACCEPTED, msgId);
405 return true;
406 }
407
408 case MAVLINK_MSG_ID_VIDEO_STREAM_STATUS:
409 {
410 if (!(cam->capFlags & CAMERA_CAP_FLAGS_HAS_VIDEO_STREAM)) {
411 _sendCommandAck(targetCompId, MAV_CMD_REQUEST_MESSAGE, MAV_RESULT_DENIED, msgId);
412 return true;
413 }
414 const uint8_t streamId = static_cast<uint8_t>(request.param2);
415 if (streamId == 0) {
416 for (uint8_t s = 1; s <= kNumStreams; s++) {
417 _sendVideoStreamStatus(targetCompId, s);
418 }
419 } else {
420 _sendVideoStreamStatus(targetCompId, streamId);
421 }
422 _sendCommandAck(targetCompId, MAV_CMD_REQUEST_MESSAGE, MAV_RESULT_ACCEPTED, msgId);
423 return true;
424 }
425
426 default:
427 break;
428 }
429
430 return false;
431}
432
433void MockLinkCamera::_sendCameraInformation(uint8_t compId)
434{
435 const CameraState *cam = _findCamera(compId);
436 if (!cam) {
437 return;
438 }
439
440 const int cameraIndex = compId - MAV_COMP_ID_CAMERA;
441
442 const uint8_t vendorName[32] = "MockLink";
443 const char cameraDefinitionUri[MAVLINK_MSG_CAMERA_INFORMATION_FIELD_CAM_DEFINITION_URI_LEN] = {};
444 const QString model = QStringLiteral("MockCam %1").arg(cameraIndex + 1);
445 QByteArray modelBA = model.toLocal8Bit();
446 modelBA.resize(MAVLINK_MSG_CAMERA_INFORMATION_FIELD_MODEL_NAME_LEN);
447
448 mavlink_message_t msg{};
449 (void) mavlink_msg_camera_information_pack_chan(
450 _mockLink->vehicleId(),
451 compId,
452 _mockLink->mavlinkChannel(),
453 &msg,
454 0, // time_boot_ms
455 vendorName,
456 reinterpret_cast<const uint8_t *>(modelBA.constData()),
457 0, // firmware_version
458 0, // focal_length
459 0, // sensor_size_h
460 0, // sensor_size_v
461 1920, // resolution_h
462 1080, // resolution_v
463 0, // lens_id
464 cam->capFlags, // flags
465 0, // cam_definition_version
466 cameraDefinitionUri, // cam_definition_uri
467 0, // gimbal_device_id
468 0); // flags (reserved)
469 _mockLink->respondWithMavlinkMessage(msg);
470
471 qCDebug(MockLinkCameraLog) << "Sent CAMERA_INFORMATION for compId:" << compId << "model:" << model;
472}
473
474void MockLinkCamera::_sendCameraSettings(uint8_t compId)
475{
476 const CameraState *cam = _findCamera(compId);
477 if (!cam) {
478 return;
479 }
480
481 mavlink_message_t msg{};
482 (void) mavlink_msg_camera_settings_pack_chan(
483 _mockLink->vehicleId(),
484 compId,
485 _mockLink->mavlinkChannel(),
486 &msg,
487 0, // time_boot_ms
488 cam->cameraMode, // mode_id
489 cam->zoomLevel, // zoomLevel
490 cam->focusLevel, // focusLevel
491 0); // camera_device_id
492 _mockLink->respondWithMavlinkMessage(msg);
493
494 qCDebug(MockLinkCameraLog) << "Sent CAMERA_SETTINGS for compId:" << compId
495 << "mode:" << cam->cameraMode
496 << "zoom:" << cam->zoomLevel
497 << "focus:" << cam->focusLevel;
498}
499
500void MockLinkCamera::_sendStorageInformation(uint8_t compId)
501{
502 const char storageName[MAVLINK_MSG_STORAGE_INFORMATION_FIELD_NAME_LEN] = {};
503
504 mavlink_message_t msg{};
505 (void) mavlink_msg_storage_information_pack_chan(
506 _mockLink->vehicleId(),
507 compId,
508 _mockLink->mavlinkChannel(),
509 &msg,
510 0, // time_boot_ms
511 1, // storage_id
512 1, // storage_count
513 STORAGE_STATUS_READY, // status
514 static_cast<float>(kStorageTotalMiB), // total_capacity (MiB)
515 static_cast<float>(kStorageTotalMiB - kStorageFreeMiB), // used_capacity (MiB)
516 static_cast<float>(kStorageFreeMiB), // available_capacity (MiB)
517 NAN, // read_speed
518 NAN, // write_speed
519 STORAGE_TYPE_SD, // type
520 storageName, // name
521 0); // storage_usage
522 _mockLink->respondWithMavlinkMessage(msg);
523
524 qCDebug(MockLinkCameraLog) << "Sent STORAGE_INFORMATION for compId:" << compId;
525}
526
527void MockLinkCamera::_sendCameraCaptureStatus(uint8_t compId)
528{
529 const CameraState *cam = _findCamera(compId);
530 if (!cam) {
531 return;
532 }
533
534 mavlink_message_t msg{};
535 (void) mavlink_msg_camera_capture_status_pack_chan(
536 _mockLink->vehicleId(),
537 compId,
538 _mockLink->mavlinkChannel(),
539 &msg,
540 0, // time_boot_ms
541 cam->image_status, // image_status (ImageCaptureStatus enum)
542 cam->recording ? 1 : 0, // video_status (0=idle, 1=running)
543 cam->image_interval, // image_interval
544 0, // recording_time_ms
545 static_cast<float>(kStorageFreeMiB), // available_capacity
546 cam->imagesCaptured, // image_count
547 0); // camera_device_id
548 _mockLink->respondWithMavlinkMessage(msg);
549
550 qCDebug(MockLinkCameraLog) << "Sent CAMERA_CAPTURE_STATUS for compId:" << compId
551 << "status:" << _imageCaptureStatusToString(cam->image_status)
552 << "interval:" << cam->image_interval
553 << "recording:" << cam->recording
554 << "images:" << cam->imagesCaptured;
555}
556
557void MockLinkCamera::_sendCameraImageCaptured(uint8_t compId)
558{
559 const CameraState *cam = _findCamera(compId);
560 if (!cam) {
561 return;
562 }
563
564 // Use vehicle's current position for image location
565 // In a real camera this would be the position when the image was taken
566 const int32_t lat = static_cast<int32_t>(_mockLink->vehicleLatitude() * 1e7);
567 const int32_t lon = static_cast<int32_t>(_mockLink->vehicleLongitude() * 1e7);
568 const float alt = static_cast<float>(_mockLink->vehicleAltitudeAMSL());
569 const float q[4] = {1.0f, 0.0f, 0.0f, 0.0f}; // quaternion (not used in this mock, set to identity)
570 const char fileUrl[MAVLINK_MSG_CAMERA_IMAGE_CAPTURED_FIELD_FILE_URL_LEN] = {};
571
572 mavlink_message_t msg{};
573 (void) mavlink_msg_camera_image_captured_pack_chan(
574 _mockLink->vehicleId(),
575 compId,
576 _mockLink->mavlinkChannel(),
577 &msg,
578 0, // time_boot_ms
579 0, // time_utc
580 (compId - MAV_COMP_ID_CAMERA) + 1, // camera_id (1-based, derived from compId)
581 lat, // lat (degrees * 1E7)
582 lon, // lon (degrees * 1E7)
583 alt, // alt (MSL)
584 alt, // relative_alt (same as MSL for simplicity)
585 q, // q (quaternion, unused)
586 cam->imagesCaptured, // image_index
587 1, // capture_result (1=success)
588 fileUrl); // file_url
589 _mockLink->respondWithMavlinkMessage(msg);
590
591 qCDebug(MockLinkCameraLog) << "Sent CAMERA_IMAGE_CAPTURED for compId:" << compId
592 << "index:" << cam->imagesCaptured;
593}
594
595void MockLinkCamera::_sendVideoStreamInformation(uint8_t compId, uint8_t streamId)
596{
597 const int cameraIndex = compId - MAV_COMP_ID_CAMERA;
598 const QString name = QStringLiteral("Stream %1-%2").arg(cameraIndex + 1).arg(streamId);
599 QByteArray nameBA = name.toLocal8Bit();
600 nameBA.resize(MAVLINK_MSG_VIDEO_STREAM_INFORMATION_FIELD_NAME_LEN);
601
602 const QString uri = QStringLiteral("udp://127.0.0.1:5600");
603 QByteArray uriBA = uri.toLocal8Bit();
604 uriBA.resize(MAVLINK_MSG_VIDEO_STREAM_INFORMATION_FIELD_URI_LEN);
605
606 mavlink_message_t msg{};
607 (void) mavlink_msg_video_stream_information_pack_chan(
608 _mockLink->vehicleId(),
609 compId,
610 _mockLink->mavlinkChannel(),
611 &msg,
612 streamId, // stream_id
613 kNumStreams, // count
614 VIDEO_STREAM_TYPE_RTPUDP, // type
615 VIDEO_STREAM_STATUS_FLAGS_RUNNING, // flags
616 30, // framerate
617 1920, // resolution_h
618 1080, // resolution_v
619 4000, // bitrate (kbit/s)
620 0, // rotation
621 70, // hfov
622 nameBA.constData(),
623 uriBA.constData(),
624 VIDEO_STREAM_ENCODING_H264,
625 0); // encoding_sub
626 _mockLink->respondWithMavlinkMessage(msg);
627
628 qCDebug(MockLinkCameraLog) << "Sent VIDEO_STREAM_INFORMATION for compId:" << compId << "stream:" << streamId;
629}
630
631void MockLinkCamera::_sendVideoStreamStatus(uint8_t compId, uint8_t streamId)
632{
633 mavlink_message_t msg{};
634 (void) mavlink_msg_video_stream_status_pack_chan(
635 _mockLink->vehicleId(),
636 compId,
637 _mockLink->mavlinkChannel(),
638 &msg,
639 streamId, // stream_id
640 VIDEO_STREAM_STATUS_FLAGS_RUNNING, // flags
641 30, // framerate
642 1920, // resolution_h
643 1080, // resolution_v
644 4000, // bitrate (kbit/s)
645 0, // rotation
646 70, // hfov
647 0); // encoding (reserved in status)
648 _mockLink->respondWithMavlinkMessage(msg);
649
650 qCDebug(MockLinkCameraLog) << "Sent VIDEO_STREAM_STATUS for compId:" << compId << "stream:" << streamId;
651}
652
653void MockLinkCamera::_sendCommandAck(uint8_t compId, uint16_t command, uint8_t result, int requestedMsgId)
654{
655 mavlink_message_t msg{};
656 (void) mavlink_msg_command_ack_pack_chan(
657 _mockLink->vehicleId(),
658 compId,
659 _mockLink->mavlinkChannel(),
660 &msg,
661 command,
662 result,
663 0, // progress
664 0, // result_param2
665 0, // target_system
666 0); // target_component
667 _mockLink->respondWithMavlinkMessage(msg);
668
669 QString commandName = MissionCommandTree::instance()->rawName(static_cast<MAV_CMD>(command));
670 QString logMsg = QStringLiteral("Sent COMMAND_ACK for compId: %1 command: %2 result: %3")
671 .arg(compId).arg(commandName).arg(result);
672
673 if (command == MAV_CMD_REQUEST_MESSAGE && requestedMsgId >= 0) {
674 const mavlink_message_info_t* info = mavlink_get_message_info_by_id(static_cast<uint32_t>(requestedMsgId));
675 QString msgName = info ? info->name : QString::number(requestedMsgId);
676 logMsg += QStringLiteral(" requestedMsg: %1").arg(msgName);
677 }
678
679 qCDebug(MockLinkCameraLog) << logMsg;
680}
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
uint8_t mavlinkChannel() const
static MissionCommandTree * instance()
QString rawName(MAV_CMD command) const
Returns the raw name for the specified command.
void sendCameraHeartbeats()
Send heartbeats for all simulated camera components (call from 1Hz tasks)
bool handleMavlinkMessage(const mavlink_message_t &msg)
void run10HzTasks()
Update camera states (call from 10Hz tasks)
@ ImageCaptureInProgress
Single image capture in progress.
@ ImageCaptureInterval
Interval capture enabled.
@ ImageCaptureIntervalCapture
Interval capture with capture in progress.
@ ImageCaptureIdle
No capture in progress.
Per-camera simulated state.
qint64 singleShotStartMs
Timestamp when single-shot capture started (0 = not active)
uint8_t image_status
ImageCaptureStatus enum.