ardupilot/libraries/AP_Camera/AP_Camera_Backend.cpp

469 lines
15 KiB
C++

#include "AP_Camera_config.h"
#if AP_CAMERA_ENABLED
#include "AP_Camera_Backend.h"
#include <GCS_MAVLink/GCS.h>
#include <AP_GPS/AP_GPS.h>
#include <AP_Mount/AP_Mount.h>
#include <AP_AHRS/AP_AHRS.h>
extern const AP_HAL::HAL& hal;
// Constructor
AP_Camera_Backend::AP_Camera_Backend(AP_Camera &frontend, AP_Camera_Params &params, uint8_t instance) :
_frontend(frontend),
_params(params),
_instance(instance)
{}
void AP_Camera_Backend::init()
{
#if AP_CAMERA_INFO_FROM_SCRIPT_ENABLED
_camera_info.focal_length = NaNf;
_camera_info.sensor_size_h = NaNf;
_camera_info.sensor_size_v = NaNf;
_camera_info.flags = CAMERA_CAP_FLAGS_CAPTURE_IMAGE;
#endif // AP_CAMERA_INFO_FROM_SCRIPT_ENABLED
}
// update - should be called at 50hz
void AP_Camera_Backend::update()
{
// Check camera options and start/stop recording based on arm/disarm
if (option_is_enabled(Option::RecordWhileArmed)) {
if (hal.util->get_soft_armed() != last_is_armed) {
last_is_armed = hal.util->get_soft_armed();
if (!record_video(last_is_armed)) {
GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "Camera: failed to %s recording", last_is_armed ? "start" : "stop");
}
}
}
// try to take picture if pending
if (trigger_pending) {
take_picture();
}
// check feedback pin
check_feedback();
// time based triggering
// if time and distance triggering both are enabled then we only do time based triggering
if (time_interval_settings.num_remaining != 0) {
uint32_t delta_ms = AP_HAL::millis() - last_picture_time_ms;
if (delta_ms > time_interval_settings.time_interval_ms) {
if (take_picture()) {
// decrease num_remaining except when its -1 i.e. capture forever
if (time_interval_settings.num_remaining > 0) {
time_interval_settings.num_remaining--;
}
}
}
return;
}
// implement trigger distance
if (!is_positive(_params.trigg_dist)) {
last_location.lat = 0;
last_location.lng = 0;
return;
}
const AP_AHRS &ahrs = AP::ahrs();
Location current_loc;
if (!ahrs.get_location(current_loc)) {
return;
}
// check vehicle flight mode supports trigg dist
if (!_frontend.vehicle_mode_ok_for_trigg_dist()) {
return;
}
// check vehicle roll angle is less than configured maximum
if ((_frontend.get_roll_max() > 0) && (fabsf(ahrs.roll_sensor * 1e-2f) > _frontend.get_roll_max())) {
return;
}
// initialise last location to current location
if (last_location.lat == 0 && last_location.lng == 0) {
last_location = current_loc;
return;
}
if (last_location.lat == current_loc.lat && last_location.lng == current_loc.lng) {
// we haven't moved - this can happen as update() may
// be called without a new GPS fix
return;
}
// check vehicle has moved at least trigg_dist meters
if (current_loc.get_distance(last_location) < _params.trigg_dist) {
return;
}
take_picture();
}
// get corresponding mount instance for the camera
uint8_t AP_Camera_Backend::get_mount_instance() const
{
// instance 0 means default
if (_params.mount_instance.get() == 0) {
return _instance;
}
return _params.mount_instance.get() - 1;
}
// get mavlink gimbal device id which is normally mount_instance+1
uint8_t AP_Camera_Backend::get_gimbal_device_id() const
{
#if HAL_MOUNT_ENABLED
const uint8_t mount_instance = get_mount_instance();
AP_Mount* mount = AP::mount();
if (mount != nullptr) {
if (mount->get_mount_type(mount_instance) != AP_Mount::Type::None) {
return (mount_instance + 1);
}
}
#endif
return 0;
}
// take a picture. returns true on success
bool AP_Camera_Backend::take_picture()
{
// setup feedback pin interrupt or timer
setup_feedback_callback();
// check minimum time interval since last picture taken
uint32_t now_ms = AP_HAL::millis();
if (now_ms - last_picture_time_ms < (uint32_t)(_params.interval_min * 1000)) {
trigger_pending = true;
return false;
}
trigger_pending = false;
// trigger actually taking picture and update image count
if (trigger_pic()) {
image_index++;
last_picture_time_ms = now_ms;
IGNORE_RETURN(AP::ahrs().get_location(last_location));
#if HAL_LOGGING_ENABLED
log_picture();
#endif
return true;
}
return false;
}
// take multiple pictures, time_interval between two consecutive pictures is in miliseconds
// total_num is number of pictures to be taken, -1 means capture forever
void AP_Camera_Backend::take_multiple_pictures(uint32_t time_interval_ms, int16_t total_num)
{
time_interval_settings = {time_interval_ms, total_num};
}
// stop capturing multiple image sequence
void AP_Camera_Backend::stop_capture()
{
time_interval_settings = {0, 0};
}
// handle camera control
void AP_Camera_Backend::control(float session, float zoom_pos, float zoom_step, float focus_lock, int32_t shooting_cmd, int32_t cmd_id)
{
// take picture
if (shooting_cmd == 1) {
take_picture();
}
}
// send camera feedback message to GCS
void AP_Camera_Backend::send_camera_feedback(mavlink_channel_t chan)
{
int32_t altitude = 0;
if (camera_feedback.location.initialised() && !camera_feedback.location.get_alt_cm(Location::AltFrame::ABSOLUTE, altitude)) {
// completely ignore this failure! this is a shouldn't-happen
// as current_loc should never be in an altitude we can't
// convert.
}
int32_t altitude_rel = 0;
if (camera_feedback.location.initialised() && !camera_feedback.location.get_alt_cm(Location::AltFrame::ABOVE_HOME, altitude_rel)) {
// completely ignore this failure! this is a shouldn't-happen
// as current_loc should never be in an altitude we can't
// convert.
}
// send camera feedback message
mavlink_msg_camera_feedback_send(
chan,
camera_feedback.timestamp_us, // image timestamp
0, // target system id
_instance, // camera id
image_index, // image index
camera_feedback.location.lat, // latitude
camera_feedback.location.lng, // longitude
altitude*1e-2f, // alt MSL
altitude_rel*1e-2f, // alt relative to home
camera_feedback.roll_sensor*1e-2f, // roll angle (deg)
camera_feedback.pitch_sensor*1e-2f, // pitch angle (deg)
camera_feedback.yaw_sensor*1e-2f, // yaw angle (deg)
0.0f, // focal length
CAMERA_FEEDBACK_PHOTO, // flags
camera_feedback.feedback_trigger_logged_count); // completed image captures
}
// send camera information message to GCS
void AP_Camera_Backend::send_camera_information(mavlink_channel_t chan) const
{
mavlink_camera_information_t camera_info;
#if AP_CAMERA_INFO_FROM_SCRIPT_ENABLED
camera_info = _camera_info;
#else
memset(&camera_info, 0, sizeof(camera_info));
camera_info.focal_length = NaNf;
camera_info.sensor_size_h = NaNf;
camera_info.sensor_size_v = NaNf;
camera_info.flags = CAMERA_CAP_FLAGS_CAPTURE_IMAGE;
#endif // AP_CAMERA_INFO_FROM_SCRIPT_ENABLED
// Set fixed fields
// lens_id is populated with the instance number, to disambiguate multiple cameras
camera_info.lens_id = _instance;
camera_info.gimbal_device_id = get_gimbal_device_id();
camera_info.time_boot_ms = AP_HAL::millis();
// Send CAMERA_INFORMATION message
mavlink_msg_camera_information_send_struct(chan, &camera_info);
}
#if AP_CAMERA_INFO_FROM_SCRIPT_ENABLED
void AP_Camera_Backend::set_camera_information(mavlink_camera_information_t camera_info)
{
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Camera %u CAMERA_INFORMATION (%s %s) set from script", _instance, camera_info.vendor_name, camera_info.model_name);
_camera_info = camera_info;
};
#endif // AP_CAMERA_INFO_FROM_SCRIPT_ENABLED
#if AP_MAVLINK_MSG_VIDEO_STREAM_INFORMATION_ENABLED
// send video stream information message to GCS
void AP_Camera_Backend::send_video_stream_information(mavlink_channel_t chan) const
{
#if AP_CAMERA_INFO_FROM_SCRIPT_ENABLED
// Send VIDEO_STREAM_INFORMATION message
mavlink_msg_video_stream_information_send_struct(chan, &_stream_info);
#endif // AP_CAMERA_INFO_FROM_SCRIPT_ENABLED
}
#endif // AP_MAVLINK_MSG_VIDEO_STREAM_INFORMATION_ENABLED
#if AP_CAMERA_INFO_FROM_SCRIPT_ENABLED
void AP_Camera_Backend::set_stream_information(mavlink_video_stream_information_t stream_info)
{
_stream_info = stream_info;
};
#endif // AP_CAMERA_INFO_FROM_SCRIPT_ENABLED
// send camera settings message to GCS
void AP_Camera_Backend::send_camera_settings(mavlink_channel_t chan) const
{
// send CAMERA_SETTINGS message
mavlink_msg_camera_settings_send(
chan,
AP_HAL::millis(), // time_boot_ms
CAMERA_MODE_IMAGE, // camera mode (0:image, 1:video, 2:image survey)
NaNf, // zoomLevel float, percentage from 0 to 100, NaN if unknown
NaNf); // focusLevel float, percentage from 0 to 100, NaN if unknown
}
#if AP_CAMERA_SEND_FOV_STATUS_ENABLED
// send camera field of view status
void AP_Camera_Backend::send_camera_fov_status(mavlink_channel_t chan) const
{
// getting corresponding mount instance for camera
AP_Mount* mount = AP::mount();
if (mount == nullptr) {
return;
}
// get latest POI from mount
Quaternion quat;
Location camera_loc;
Location poi_loc;
const bool have_poi_loc = mount->get_poi(get_mount_instance(), quat, camera_loc, poi_loc);
// if failed to get POI, get camera location directly from AHRS
// and attitude directly from mount
bool have_camera_loc = have_poi_loc;
if (!have_camera_loc) {
have_camera_loc = AP::ahrs().get_location(camera_loc);
mount->get_attitude_quaternion(get_mount_instance(), quat);
}
// calculate attitude quaternion in earth frame using AHRS yaw
Quaternion quat_ef;
quat_ef.from_euler(0, 0, AP::ahrs().get_yaw());
quat_ef *= quat;
// send camera fov status message only if the last calculated values aren't stale
const float quat_array[4] = {
quat_ef.q1,
quat_ef.q2,
quat_ef.q3,
quat_ef.q4
};
mavlink_msg_camera_fov_status_send(
chan,
AP_HAL::millis(),
have_camera_loc ? camera_loc.lat : INT32_MAX,
have_camera_loc ? camera_loc.lng : INT32_MAX,
have_camera_loc ? camera_loc.alt * 10 : INT32_MAX,
have_poi_loc ? poi_loc.lat : INT32_MAX,
have_poi_loc ? poi_loc.lng : INT32_MAX,
have_poi_loc ? poi_loc.alt * 10 : INT32_MAX,
quat_array,
horizontal_fov() > 0 ? horizontal_fov() : NaNf,
vertical_fov() > 0 ? vertical_fov() : NaNf
);
}
#endif
// send camera capture status message to GCS
void AP_Camera_Backend::send_camera_capture_status(mavlink_channel_t chan) const
{
// Current status of image capturing (0: idle, 1: capture in progress, 2: interval set but idle, 3: interval set and capture in progress)
const uint8_t image_status = (time_interval_settings.num_remaining > 0) ? 2 : 0;
// send CAMERA_CAPTURE_STATUS message
mavlink_msg_camera_capture_status_send(
chan,
AP_HAL::millis(),
image_status,
0, // current status of video capturing (0: idle, 1: capture in progress)
static_cast<float>(time_interval_settings.time_interval_ms) / 1000.0, // image capture interval (s)
0, // elapsed time since recording started (ms)
NaNf, // available storage capacity (ms)
image_index); // total number of images captured
}
// setup a callback for a feedback pin. When on PX4 with the right FMU
// mode we can use the microsecond timer.
void AP_Camera_Backend::setup_feedback_callback()
{
if (_params.feedback_pin <= 0 || timer_installed || isr_installed) {
// invalid or already installed
return;
}
// ensure we are in input mode
hal.gpio->pinMode(_params.feedback_pin, HAL_GPIO_INPUT);
// enable pullup/pulldown
uint8_t trigger_polarity = _params.feedback_polarity == 0 ? 0 : 1;
hal.gpio->write(_params.feedback_pin, !trigger_polarity);
if (hal.gpio->attach_interrupt(_params.feedback_pin, FUNCTOR_BIND_MEMBER(&AP_Camera_Backend::feedback_pin_isr, void, uint8_t, bool, uint32_t),
trigger_polarity?AP_HAL::GPIO::INTERRUPT_RISING:AP_HAL::GPIO::INTERRUPT_FALLING)) {
isr_installed = true;
} else {
// install a 1kHz timer to check feedback pin
hal.scheduler->register_timer_process(FUNCTOR_BIND_MEMBER(&AP_Camera_Backend::feedback_pin_timer, void));
timer_installed = true;
}
}
// interrupt handler for interrupt based feedback trigger
void AP_Camera_Backend::feedback_pin_isr(uint8_t pin, bool high, uint32_t timestamp_us)
{
feedback_trigger_timestamp_us = timestamp_us;
feedback_trigger_count++;
}
// check if feedback pin is high for timer based feedback trigger, when
// attach_interrupt fails
void AP_Camera_Backend::feedback_pin_timer()
{
uint8_t pin_state = hal.gpio->read(_params.feedback_pin);
uint8_t trigger_polarity = _params.feedback_polarity == 0 ? 0 : 1;
if (pin_state == trigger_polarity &&
last_pin_state != trigger_polarity) {
feedback_trigger_timestamp_us = AP_HAL::micros();
feedback_trigger_count++;
}
last_pin_state = pin_state;
}
// check for feedback pin update and log if necessary
void AP_Camera_Backend::check_feedback()
{
if (feedback_trigger_logged_count != feedback_trigger_count) {
#if HAL_LOGGING_ENABLED
const uint32_t timestamp32 = feedback_trigger_timestamp_us;
#endif
feedback_trigger_logged_count = feedback_trigger_count;
// we should consider doing this inside the ISR and pin_timer
prep_mavlink_msg_camera_feedback(feedback_trigger_timestamp_us);
#if HAL_LOGGING_ENABLED
// log camera message
uint32_t tdiff = AP_HAL::micros() - timestamp32;
uint64_t timestamp = AP_HAL::micros64();
Write_Camera(timestamp - tdiff);
#endif
}
}
void AP_Camera_Backend::prep_mavlink_msg_camera_feedback(uint64_t timestamp_us)
{
const AP_AHRS &ahrs = AP::ahrs();
if (!ahrs.get_location(camera_feedback.location)) {
// completely ignore this failure! AHRS will provide its best guess.
}
camera_feedback.timestamp_us = timestamp_us;
camera_feedback.roll_sensor = ahrs.roll_sensor;
camera_feedback.pitch_sensor = ahrs.pitch_sensor;
camera_feedback.yaw_sensor = ahrs.yaw_sensor;
camera_feedback.feedback_trigger_logged_count = feedback_trigger_logged_count;
GCS_SEND_MESSAGE(MSG_CAMERA_FEEDBACK);
}
#if HAL_LOGGING_ENABLED
// log picture
void AP_Camera_Backend::log_picture()
{
const bool using_feedback_pin = _params.feedback_pin > 0;
if (!using_feedback_pin) {
// if we're using a feedback pin then when the event occurs we
// stash the feedback data. Since we're not using a feedback
// pin, we just use "now".
prep_mavlink_msg_camera_feedback(AP::gps().time_epoch_usec());
}
if (!using_feedback_pin) {
Write_Camera();
} else {
Write_Trigger();
}
}
#endif
#endif // AP_CAMERA_ENABLED