Files
ardupilot/libraries/AP_Camera/AP_Camera_SoloGimbal.cpp
T
Andrew Tridgell 6434442671 AP_Camera: relay MAVLink cameras and resolve camera selectors
Cache bounded stream lists and resume replies independently on each
GCS link after transmit backpressure. Expire remote capture status
after three seconds, retain locally scheduled interval capture, and
back off unanswered status requests.

Preserve native camera identity in cached replies and isolated-link
broadcasts, and suppress synthetic FC-owned status for native cameras.
Restore configured mount associations in cached information only when
a same-system camera advertises none.

Add CAMn_COMPID (0 or 7-255) so camera-v2 discovery can use a
non-default component ID. The first slot owns a colliding ID; duplicate
or invalid IDs warn and disable discovery.

Resolve command selectors as component IDs or legacy slots 1-6 with the
same rules for live commands and missions. NaN in formerly reserved
selector fields means unset. Video commands with a nonzero camera
selector treat param1 as a stream ID and pass the status frequency
through; without one they keep the old camera-slot interpretation.

Fill camera_device_id in camera information, settings, capture status,
field of view and stream messages, and document the routing and
selector design.
2026-09-23 08:59:12 +10:00

121 lines
5.1 KiB
C++

#include "AP_Camera_SoloGimbal.h"
#if AP_CAMERA_SOLOGIMBAL_ENABLED
#include <GCS_MAVLink/GCS.h>
// Toggle the shutter on the GoPro
// This is so ArduPilot can toggle the shutter directly, either for mission/GCS commands, or when the
// Solo's gimbal is installed on a vehicle other than a Solo. The usual GoPro controls through the
// Solo app and Solo controller do not use this, as it is done offboard on the companion computer.
// entry point to actually take a picture. returns true on success
bool AP_Camera_SoloGimbal::trigger_pic()
{
if (gopro_status != GOPRO_HEARTBEAT_STATUS_CONNECTED) {
GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "GoPro Not Available");
return false;
}
const uint8_t gopro_shutter_start[4] = { 1, 0, 0, 0};
const uint8_t gopro_shutter_stop[4] = { 0, 0, 0, 0};
if (gopro_capture_mode == GOPRO_CAPTURE_MODE_PHOTO) {
// Trigger shutter start to take a photo
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "GoPro Photo Trigger");
mavlink_msg_gopro_set_request_send(heartbeat_channel, mavlink_system.sysid, MAV_COMP_ID_GIMBAL,GOPRO_COMMAND_SHUTTER,gopro_shutter_start);
} else if (gopro_capture_mode == GOPRO_CAPTURE_MODE_VIDEO) {
if (gopro_is_recording) {
// GoPro is recording, so stop recording
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "GoPro Recording Stop");
mavlink_msg_gopro_set_request_send(heartbeat_channel, mavlink_system.sysid, MAV_COMP_ID_GIMBAL,GOPRO_COMMAND_SHUTTER,gopro_shutter_stop);
} else {
// GoPro is not recording, so start recording
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "GoPro Recording Start");
mavlink_msg_gopro_set_request_send(heartbeat_channel, mavlink_system.sysid, MAV_COMP_ID_GIMBAL,GOPRO_COMMAND_SHUTTER,gopro_shutter_start);
}
} else {
GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "GoPro Unsupported Capture Mode");
return false;
}
return true;
}
// Cycle the GoPro capture mode
// This is so ArduPilot can cycle through the capture modes of the GoPro directly, probably with an RC Aux function.
// This is primarily for Solo's gimbal being installed on a vehicle other than a Solo. The usual GoPro controls
// through the Solo app and Solo controller do not use this, as it is done offboard on the companion computer.
// momentary switch to change camera between picture and video modes
void AP_Camera_SoloGimbal::cam_mode_toggle()
{
uint8_t gopro_capture_mode_values[4] = { };
if (gopro_status != GOPRO_HEARTBEAT_STATUS_CONNECTED) {
GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "GoPro Not Available");
return;
}
switch(gopro_capture_mode) {
case GOPRO_CAPTURE_MODE_VIDEO:
if (gopro_is_recording) {
// GoPro is recording, cannot change modes
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "GoPro recording, can't change modes");
} else {
// Change to camera mode
gopro_capture_mode_values[0] = GOPRO_CAPTURE_MODE_PHOTO;
mavlink_msg_gopro_set_request_send(heartbeat_channel, mavlink_system.sysid, MAV_COMP_ID_GIMBAL,GOPRO_COMMAND_CAPTURE_MODE,gopro_capture_mode_values);
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "GoPro changing to mode photo");
}
break;
case GOPRO_CAPTURE_MODE_PHOTO:
default:
// Change to video mode
gopro_capture_mode_values[0] = GOPRO_CAPTURE_MODE_VIDEO;
mavlink_msg_gopro_set_request_send(heartbeat_channel, mavlink_system.sysid, MAV_COMP_ID_GIMBAL,GOPRO_COMMAND_CAPTURE_MODE,gopro_capture_mode_values);
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "GoPro changing to mode video");
break;
}
}
// handle incoming heartbeat from the Solo gimbal GoPro
void AP_Camera_SoloGimbal::handle_message(mavlink_channel_t chan, const mavlink_message_t &msg)
{
if (msg.msgid != MAVLINK_MSG_ID_GOPRO_HEARTBEAT) {
return;
}
mavlink_gopro_heartbeat_t report_msg;
mavlink_msg_gopro_heartbeat_decode(&msg, &report_msg);
gopro_is_recording = report_msg.flags & GOPRO_FLAG_RECORDING;
heartbeat_channel = chan;
switch((GOPRO_HEARTBEAT_STATUS)report_msg.status) {
case GOPRO_HEARTBEAT_STATUS_DISCONNECTED:
case GOPRO_HEARTBEAT_STATUS_INCOMPATIBLE:
case GOPRO_HEARTBEAT_STATUS_ERROR:
case GOPRO_HEARTBEAT_STATUS_CONNECTED:
gopro_status = (GOPRO_HEARTBEAT_STATUS)report_msg.status;
break;
case GOPRO_HEARTBEAT_STATUS_ENUM_END:
break;
}
switch((GOPRO_CAPTURE_MODE)report_msg.capture_mode){
case GOPRO_CAPTURE_MODE_VIDEO:
case GOPRO_CAPTURE_MODE_PHOTO:
case GOPRO_CAPTURE_MODE_BURST:
case GOPRO_CAPTURE_MODE_TIME_LAPSE:
case GOPRO_CAPTURE_MODE_MULTI_SHOT:
case GOPRO_CAPTURE_MODE_PLAYBACK:
case GOPRO_CAPTURE_MODE_SETUP:
case GOPRO_CAPTURE_MODE_UNKNOWN:
gopro_capture_mode = (GOPRO_CAPTURE_MODE)report_msg.capture_mode;
break;
case GOPRO_CAPTURE_MODE_ENUM_END:
break;
}
}
#endif // AP_CAMERA_SOLOGIMBAL_ENABLED