#pragma once
#include "AP_Camera_config.h"
#include "AP_Camera_Backend.h"
#if AP_CAMERA_RUNCAM_ENABLED
#include <AP_Param/AP_Param.h>
#include <RC_Channel/RC_Channel.h>
#include <AP_Arming/AP_Arming.h>
#include <AP_OSD/AP_OSD.h>
#define RUNCAM_MAX_PACKET_SIZE 64
#define RUNCAM_DEFAULT_BUTTON_PRESS_DELAY 300
#define RUNCAM_5KEY_BUTTON_PRESS_DELAY 100
class AP_RunCam : public AP_Camera_Backend
{
public:
AP_RunCam(AP_Camera &frontend, AP_Camera_Params ¶ms, uint8_t instance, uint8_t runcam_instance);
CLASS_NO_COPY(AP_RunCam);
static AP_RunCam *get_singleton() {
return _singleton;
}
enum class DeviceModel {
Disabled = 0,
SplitMicro = 1,
Split = 2,
Split4k = 3,
Hybrid = 4,
Run24k = 5,
};
enum class ControlOperation {
RCDEVICE_PROTOCOL_SIMULATE_WIFI_BTN = 0x00,
RCDEVICE_PROTOCOL_SIMULATE_POWER_BTN = 0x01,
RCDEVICE_PROTOCOL_CHANGE_MODE = 0x02,
RCDEVICE_PROTOCOL_CHANGE_START_RECORDING = 0x03,
RCDEVICE_PROTOCOL_CHANGE_STOP_RECORDING = 0x04,
UNKNOWN_CAMERA_OPERATION = 0xFF
};
enum class ControlOption {
STICK_YAW_RIGHT = (1 << 0),
STICK_ROLL_RIGHT = (1 << 1),
THREE_POS_SWITCH = (1 << 2),
TWO_POS_SWITCH = (1 << 3),
VIDEO_RECORDING_AT_BOOT = (1 << 4)
};
bool healthy() const override;
void cam_mode_toggle() override;
bool trigger_pic() override;
void send_camera_information(mavlink_channel_t chan) const override;
void send_camera_settings(mavlink_channel_t chan) const override;
void init() override;
bool simulate_camera_button(const ControlOperation operation, const uint32_t transition_timeout = RUNCAM_DEFAULT_BUTTON_PRESS_DELAY);
void start_recording();
void stop_recording();
bool record_video(bool _start_recording) override {
if (_start_recording) {
start_recording();
} else {
stop_recording();
}
return true;
}
void enter_osd();
void exit_osd();
void osd_option();
void update() override;
bool pre_arm_check(char *failure_msg, const uint8_t failure_msg_len) const;
static const struct AP_Param::GroupInfo var_info[];
private:
enum class Feature {
RCDEVICE_PROTOCOL_FEATURE_SIMULATE_POWER_BUTTON = (1 << 0),
RCDEVICE_PROTOCOL_FEATURE_SIMULATE_WIFI_BUTTON = (1 << 1),
RCDEVICE_PROTOCOL_FEATURE_CHANGE_MODE = (1 << 2),
RCDEVICE_PROTOCOL_FEATURE_SIMULATE_5_KEY_OSD_CABLE = (1 << 3),
RCDEVICE_PROTOCOL_FEATURE_DEVICE_SETTINGS_ACCESS = (1 << 4),
RCDEVICE_PROTOCOL_FEATURE_DISPLAY_PORT = (1 << 5),
RCDEVICE_PROTOCOL_FEATURE_START_RECORDING = (1 << 6),
RCDEVICE_PROTOCOL_FEATURE_STOP_RECORDING = (1 << 7),
FEATURES_OVERRIDE = (1 << 14)
};
const uint16_t RCDEVICE_PROTOCOL_FEATURE_2_KEY_OSD =
uint16_t(Feature::RCDEVICE_PROTOCOL_FEATURE_CHANGE_MODE)
| uint16_t(Feature::RCDEVICE_PROTOCOL_FEATURE_SIMULATE_WIFI_BUTTON)
| uint16_t(Feature::RCDEVICE_PROTOCOL_FEATURE_SIMULATE_POWER_BUTTON);
enum class Command {
RCDEVICE_PROTOCOL_COMMAND_GET_DEVICE_INFO = 0x00,
RCDEVICE_PROTOCOL_COMMAND_CAMERA_CONTROL = 0x01,
RCDEVICE_PROTOCOL_COMMAND_5KEY_SIMULATION_PRESS = 0x02,
RCDEVICE_PROTOCOL_COMMAND_5KEY_SIMULATION_RELEASE = 0x03,
RCDEVICE_PROTOCOL_COMMAND_5KEY_CONNECTION = 0x04,
COMMAND_NONE
};
enum class ConnectionOperation {
RCDEVICE_PROTOCOL_5KEY_FUNCTION_OPEN = 0x01,
RCDEVICE_PROTOCOL_5KEY_FUNCTION_CLOSE = 0x02
};
enum class SimulationOperation {
SIMULATION_NONE = 0x00,
RCDEVICE_PROTOCOL_5KEY_SIMULATION_SET = 0x01,
RCDEVICE_PROTOCOL_5KEY_SIMULATION_LEFT = 0x02,
RCDEVICE_PROTOCOL_5KEY_SIMULATION_RIGHT = 0x03,
RCDEVICE_PROTOCOL_5KEY_SIMULATION_UP = 0x04,
RCDEVICE_PROTOCOL_5KEY_SIMULATION_DOWN = 0x05
};
enum class ProtocolVersion {
RCSPLIT_VERSION = 0x00,
VERSION_1_0 = 0x01,
UNKNOWN
};
enum class RequestStatus {
NONE,
PENDING,
SUCCESS,
INCORRECT_CRC,
TIMEOUT
};
enum class State {
INITIALIZING,
INITIALIZED,
READY,
VIDEO_RECORDING,
ENTERING_MENU,
IN_MENU,
EXITING_MENU
};
enum class Event {
NONE,
ENTER_MENU,
EXIT_MENU,
IN_MENU_ENTER,
IN_MENU_RIGHT,
IN_MENU_UP,
IN_MENU_DOWN,
IN_MENU_EXIT,
BUTTON_RELEASE,
STOP_RECORDING,
START_RECORDING
};
enum class OSDOption {
NONE,
ENTER,
EXIT,
OPTION,
NO_OPTION
};
enum class VideoOption {
NOT_RECORDING = 0,
RECORDING = 1
};
enum class ButtonState {
NONE,
PRESSED,
RELEASED
};
static const uint8_t RUNCAM_NUM_SUB_MENUS = 5;
static const uint8_t RUNCAM_NUM_EXPECTED_RESPONSES = 4;
static const uint8_t RUNCAM_MAX_MENUS = 1;
static const uint8_t RUNCAM_MAX_MENU_LENGTH = 6;
static const uint8_t RUNCAM_MAX_DEVICE_TYPES = 5;
AP_Int16 _features;
AP_Int32 _boot_delay_ms;
AP_Int32 _button_delay_ms;
AP_Int32 _mode_delay_ms;
AP_Int8 _cam_type;
AP_Int8 _cam_control_option;
VideoOption _video_recording = VideoOption::NOT_RECORDING;
ProtocolVersion _protocol_version = ProtocolVersion::UNKNOWN;
AP_HAL::UARTDriver *uart;
State _state = State::INITIALIZING;
uint32_t _last_osd_update_ms;
uint32_t _transition_start_ms;
uint32_t _transition_timeout_ms;
Event _last_rc_event;
State _last_state = State::INITIALIZING;
OSDOption _last_osd_option = OSDOption::NONE;
int8_t _last_in_menu;
VideoOption _last_video_recording = VideoOption::NOT_RECORDING;
ButtonState _button_pressed = ButtonState::NONE;
bool _waiting_device_response;
OSDOption _osd_option;
int8_t _in_menu;
int8_t _menu_enter_level;
int8_t _top_menu_pos;
uint8_t _sub_menu_pos;
static uint8_t _sub_menu_lengths[RUNCAM_NUM_SUB_MENUS];
uint8_t _recv_buf[RUNCAM_MAX_PACKET_SIZE];
uint8_t _runcam_instance;
static const char* _models[RUNCAM_MAX_DEVICE_TYPES];
class Request;
FUNCTOR_TYPEDEF(parse_func_t, void, const Request&);
class Request
{
friend class AP_RunCam;
public:
Request(AP_RunCam *device, Command commandID, uint8_t param,
uint32_t timeout, uint16_t maxRetryTimes, parse_func_t parserFunc);
Request() { _command = Command::COMMAND_NONE; }
uint8_t *_recv_buf;
AP_RunCam *_device;
Command _command;
uint8_t _param;
private:
uint8_t _recv_response_length;
uint8_t _expected_response_length;
uint32_t _timeout_ms;
uint32_t _request_timestamp_ms;
uint16_t _max_retry_times;
parse_func_t _parser_func;
RequestStatus _result;
uint8_t get_expected_response_length(const Command command) const;
uint8_t get_crc() const;
void parse_response() {
if (_parser_func != nullptr) {
_parser_func(*this);
}
}
struct Length {
Command command;
uint8_t reponse_length;
};
static Length _expected_responses_length[RUNCAM_NUM_EXPECTED_RESPONSES];
} _pending_request;
struct Menu {
uint8_t _top_menu_length;
uint8_t _sub_menu_lengths[RUNCAM_MAX_MENU_LENGTH];
};
static Menu _menus[RUNCAM_MAX_DEVICE_TYPES];
uint8_t get_top_menu_length() const {
return _menus[_cam_type - 1]._top_menu_length;
}
uint8_t get_sub_menu_length(uint8_t submenu) const {
return _menus[_cam_type - 1]._sub_menu_lengths[submenu];
}
void disable_osd() {
#if OSD_ENABLED
AP_OSD* osd = AP::osd();
if (osd != nullptr) {
osd->disable();
}
#endif
}
void enable_osd() {
#if OSD_ENABLED
AP_OSD* osd = AP::osd();
if (osd != nullptr) {
osd->enable();
}
#endif
}
void update_osd();
void update_state_machine_armed();
void update_state_machine_disarmed();
void handle_initialized(Event ev);
void handle_ready(Event ev);
void handle_recording(Event ev);
void handle_in_menu(Event ev);
AP_RunCam::Event map_rc_input_to_event() const;
void handle_2_key_simulation_process(Event ev);
void exit_2_key_osd_menu();
void handle_5_key_simulation_process(Event ev);
void handle_5_key_simulation_response(const Request& request);
ControlOperation start_recording_command() const;
ControlOperation stop_recording_command() const;
void receive();
void drain();
void start_uart();
void get_device_info();
SimulationOperation map_key_to_protocol_operation(const Event ev) const;
void send_5_key_OSD_cable_simulation_event(const Event key, const uint32_t transition_timeout = RUNCAM_5KEY_BUTTON_PRESS_DELAY);
void open_5_key_OSD_cable_connection(parse_func_t parseFunc);
void close_5_key_OSD_cable_connection(parse_func_t parseFunc);
void simulate_5_key_OSD_cable_button_press(const SimulationOperation operation, parse_func_t parseFunc);
void simulate_5_key_OSD_cable_button_release(parse_func_t parseFunc);
void send_request_and_waiting_response(Command commandID, uint8_t param, uint32_t timeout,
uint16_t maxRetryTimes, parse_func_t parseFunc);
void send_packet(Command command, uint8_t param);
void parse_device_info(const Request& request);
bool camera_ready() const;
bool has_feature(const Feature feature) const { return _features.get() & uint16_t(feature); }
bool has_2_key_OSD() const {
return (_features.get() & RCDEVICE_PROTOCOL_FEATURE_2_KEY_OSD) == RCDEVICE_PROTOCOL_FEATURE_2_KEY_OSD;
}
bool has_5_key_OSD() const {
return !has_2_key_OSD() && has_feature(Feature::RCDEVICE_PROTOCOL_FEATURE_SIMULATE_5_KEY_OSD_CABLE);
}
bool is_arming_prevented() const { return _in_menu > _menu_enter_level; }
void simulation_OSD_cable_failed(const Request& request);
bool request_pending(uint32_t now);
static AP_RunCam *_singleton;
};
namespace AP
{
AP_RunCam *runcam();
};
#endif