LCOV - code coverage report
Current view: top level - AP_Camera - AP_RunCam.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 25 542 4.6 %
Date: 2026-09-09 11:02:08 Functions: 5 49 10.2 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : /*
      16             :   implementation of RunCam camera protocols 
      17             : 
      18             :   With thanks to betaflight for a great reference
      19             :   implementation. Several of the functions below are based on
      20             :   betaflight equivalent functions
      21             : 
      22             :   RunCam protocol specification can be found at https://support.runcam.com/hc/en-us/articles/360014537794-RunCam-Device-Protocol
      23             :  */
      24             : #include "AP_RunCam.h"
      25             : 
      26             : #if AP_CAMERA_RUNCAM_ENABLED
      27             : 
      28             : #include <AP_Math/AP_Math.h>
      29             : #include <AP_Math/crc.h>
      30             : #include <GCS_MAVLink/GCS.h>
      31             : #include <AP_Logger/AP_Logger.h>
      32             : #include <AP_SerialManager/AP_SerialManager.h>
      33             : 
      34             : const AP_Param::GroupInfo AP_RunCam::var_info[] = {
      35             :     // @Param: TYPE
      36             :     // @DisplayName: RunCam device type
      37             :     // @Description: RunCam device type used to determine OSD menu structure and shutter options.
      38             :     // @Values: 0:Disabled, 1:RunCam Split Micro/RunCam with UART, 2:RunCam Split, 3:RunCam Split4 4k, 4:RunCam Hybrid/RunCam Thumb Pro, 5:Runcam 2 4k
      39             :     AP_GROUPINFO_FLAGS("TYPE", 1, AP_RunCam, _cam_type, int(DeviceModel::SplitMicro), AP_PARAM_FLAG_ENABLE),
      40             : 
      41             :     // @Param: FEATURES
      42             :     // @DisplayName: RunCam features available
      43             :     // @Description: The available features of the attached RunCam device. If 0 then the RunCam device will be queried for the features it supports, otherwise this setting is used.
      44             :     // @User: Advanced
      45             :     // @Bitmask: 0:Power Button,1:WiFi Button,2:Change Mode,3:5-Key OSD,4:Settings Access,5:DisplayPort,6:Start Recording,7:Stop Recording
      46             :     AP_GROUPINFO("FEATURES", 2, AP_RunCam, _features, 0),
      47             : 
      48             :     // @Param: BT_DELAY
      49             :     // @DisplayName: RunCam boot delay before allowing updates
      50             :     // @Description: Time it takes for the RunCam to become fully ready in ms. If this is too short then commands can get out of sync.
      51             :     // @User: Advanced
      52             :     AP_GROUPINFO("BT_DELAY", 3, AP_RunCam, _boot_delay_ms, 7000),
      53             : 
      54             :     // @Param: BTN_DELY
      55             :     // @DisplayName: RunCam button delay before allowing further button presses
      56             :     // @Description: Time it takes for the a RunCam button press to be actived in ms. If this is too short then commands can get out of sync.
      57             :     // @User: Advanced
      58             :     AP_GROUPINFO("BTN_DELY", 4, AP_RunCam, _button_delay_ms, RUNCAM_DEFAULT_BUTTON_PRESS_DELAY),
      59             : 
      60             :     // @Param: MDE_DELY
      61             :     // @DisplayName: RunCam mode delay before allowing further button presses
      62             :     // @Description: Time it takes for the a RunCam mode button press to be actived in ms. If a mode change first requires a video recording change then double this value is used. If this is too short then commands can get out of sync.
      63             :     // @User: Advanced
      64             :     AP_GROUPINFO("MDE_DELY", 5, AP_RunCam, _mode_delay_ms, 800),
      65             : 
      66             :     // @Param: CONTROL
      67             :     // @DisplayName: RunCam control option
      68             :     // @Description: Specifies the allowed actions required to enter the OSD menu and other option like autorecording
      69             :     // @Bitmask: 0:Stick yaw right,1:Stick roll right,2:3-position switch,3:2-position switch,4:Autorecording enabled
      70             :     // @User: Advanced
      71             :     AP_GROUPINFO("CONTROL", 6, AP_RunCam, _cam_control_option, uint8_t(ControlOption::STICK_ROLL_RIGHT) | uint8_t(ControlOption::TWO_POS_SWITCH)),
      72             : 
      73             :     AP_GROUPEND
      74             : };
      75             : 
      76             : #define RUNCAM_DEBUG 0
      77             : 
      78             : #if RUNCAM_DEBUG
      79             : static const char* event_names[11] = {
      80             :         "NONE", "ENTER_MENU", "EXIT_MENU",
      81             :         "IN_MENU_ENTER", "IN_MENU_RIGHT", "IN_MENU_UP", "IN_MENU_DOWN", "IN_MENU_EXIT",
      82             :         "BUTTON_RELEASE", "STOP_RECORDING", "START_RECORDING"
      83             : };
      84             : static const char* state_names[7] = {
      85             :         "INITIALIZING", "INITIALIZED", "READY", "VIDEO_RECORDING", "ENTERING_MENU", "IN_MENU", "EXITING_MENU"
      86             : };
      87             : #define debug(fmt, args ...) do { hal.console->printf("RunCam[%s]: " fmt, state_names[int(_state)], ## args); } while (0)
      88             : #else
      89             : #define debug(fmt, args ...)
      90             : #endif
      91             : 
      92             : extern const AP_HAL::HAL& hal;
      93             : 
      94             : // singleton instance
      95             : AP_RunCam *AP_RunCam::_singleton;
      96             : 
      97             : AP_RunCam::Request::Length AP_RunCam::Request::_expected_responses_length[RUNCAM_NUM_EXPECTED_RESPONSES] = {
      98             :     { Command::RCDEVICE_PROTOCOL_COMMAND_GET_DEVICE_INFO, 5 },
      99             :     { Command::RCDEVICE_PROTOCOL_COMMAND_5KEY_SIMULATION_PRESS, 2 },
     100             :     { Command::RCDEVICE_PROTOCOL_COMMAND_5KEY_SIMULATION_RELEASE, 2 },
     101             :     { Command::RCDEVICE_PROTOCOL_COMMAND_5KEY_CONNECTION, 3 },
     102             : };
     103             : 
     104             : // the protocol for Runcam Device definition
     105             : static const uint8_t RUNCAM_HEADER = 0xCC;
     106             : static const uint8_t RUNCAM_OSD_MENU_DEPTH = 2;
     107             : static const uint32_t RUNCAM_INIT_INTERVAL_MS = 1000;
     108             : static const uint32_t RUNCAM_OSD_UPDATE_INTERVAL_MS = 100; // 10Hz
     109             : 
     110             : // menu structures of runcam devices
     111             : AP_RunCam::Menu AP_RunCam::_menus[RUNCAM_MAX_DEVICE_TYPES] = {
     112             :     // these are correct for the runcam split micro v2.4.4, others may vary
     113             :     // Video, Image, TV-OUT, Micro SD Card, General
     114             :     { 6, { 5, 8, 3, 3, 7 }}, // SplitMicro
     115             :     { 0, { 0 }}, // Split
     116             :     { 6, { 4, 10, 3, 3, 7 }}, // Split4 4K
     117             :     { 1, { 0 }}, // Hybrid, simple mode switch
     118             :         { 6, { 3, 10, 2, 2, 8 }}, // Runcam 2 4K
     119             : };
     120             : 
     121             : const char* AP_RunCam::_models[RUNCAM_MAX_DEVICE_TYPES] = {
     122             :     "SplitMicro",
     123             :     "Split",
     124             :     "Split4k",
     125             :     "Hybrid",
     126             :     "Run24k"
     127             : };
     128             : 
     129           6 : AP_RunCam::AP_RunCam(AP_Camera &frontend, AP_Camera_Params &params, uint8_t instance, uint8_t runcam_instance)
     130           6 :     : AP_Camera_Backend(frontend, params, instance), _runcam_instance(runcam_instance)
     131             : {
     132           6 :     AP_Param::setup_object_defaults(this, var_info);
     133           6 :     if (_singleton != nullptr && _singleton->_instance == instance) {
     134           0 :         AP_HAL::panic("AP_RunCam instance must be a singleton %u", instance);
     135             :     }
     136           6 :     if (_singleton == nullptr) {
     137           6 :         _singleton = this;
     138             :     }
     139           6 :     _cam_type.set(constrain_int16(_cam_type, 0, RUNCAM_MAX_DEVICE_TYPES));
     140           6 :     _video_recording = VideoOption(_cam_control_option & uint8_t(ControlOption::VIDEO_RECORDING_AT_BOOT));
     141           6 : }
     142             : 
     143             : // init the runcam device by finding a serial device configured for the RunCam protocol
     144           6 : void AP_RunCam::init()
     145             : {
     146           6 :     AP_SerialManager *serial_manager = AP_SerialManager::get_singleton();
     147           6 :     if (serial_manager) {
     148           6 :         uart = serial_manager->find_serial(AP_SerialManager::SerialProtocol_RunCam, _runcam_instance);
     149             :     }
     150           6 :     if (uart != nullptr) {
     151             :         /*
     152             :           if the user has setup a serial port as a runcam then default
     153             :           type to the split micro (Andy's development platform!). This makes setup a bit easier for most
     154             :           users while still enabling parameters to be hidden for users
     155             :           without a RunCam
     156             :          */
     157           0 :         _cam_type.set_default(int8_t(DeviceModel::SplitMicro));
     158           0 :         AP_Param::invalidate_count();
     159             :     }
     160           6 :     if (_cam_type.get() == int8_t(DeviceModel::Disabled)) {
     161           0 :         uart = nullptr;
     162           0 :         return;
     163             :     }
     164             : 
     165           6 :     if (uart == nullptr) {
     166           6 :         return;
     167             :     }
     168             : 
     169             :     // Split and Runcam 2 4k requires two mode presses to get into the menu
     170           0 :     if (_cam_type.get() == int8_t(DeviceModel::Split) || _cam_type.get() == int8_t(DeviceModel::Run24k)) {
     171           0 :         _menu_enter_level = -1;
     172           0 :         _in_menu = -1;
     173             :     }
     174             : 
     175           0 :     start_uart();
     176             : 
     177             :     // first transition is from initialized to ready
     178           0 :     _transition_start_ms = AP_HAL::millis();
     179           0 :     _transition_timeout_ms = _boot_delay_ms;
     180             : 
     181           0 :     get_device_info();
     182             : }
     183             : 
     184             : // simulate pressing the camera button
     185           0 : bool AP_RunCam::simulate_camera_button(const ControlOperation operation, const uint32_t transition_timeout)
     186             : {
     187           0 :     if (!uart || _protocol_version != ProtocolVersion::VERSION_1_0) {
     188           0 :         return false;
     189             :     }
     190             : 
     191           0 :     _transition_timeout_ms = transition_timeout;
     192             :     debug("press button %d, timeout=%dms\n", int(operation), int(transition_timeout));
     193           0 :     send_packet(Command::RCDEVICE_PROTOCOL_COMMAND_CAMERA_CONTROL, uint8_t(operation));
     194             : 
     195           0 :     return true;
     196             : }
     197             : 
     198             : // start the video
     199           0 : void AP_RunCam::start_recording() {
     200             :     debug("start recording(%d)\n", int(_state));
     201           0 :     _video_recording = VideoOption::RECORDING;
     202           0 :     _osd_option = OSDOption::NO_OPTION;
     203           0 : }
     204             : 
     205             : // stop the video
     206           0 : void AP_RunCam::stop_recording() {
     207             :     debug("stop recording(%d)\n", int(_state));
     208           0 :     _video_recording = VideoOption::NOT_RECORDING;
     209           0 :     _osd_option = OSDOption::NO_OPTION;
     210           0 : }
     211             : 
     212             : // enter the OSD menu
     213           0 : void AP_RunCam::enter_osd()
     214             : {
     215             :     debug("enter osd(%d)\n", int(_state));
     216           0 :     _osd_option = OSDOption::ENTER;
     217           0 : }
     218             : 
     219             : // exit the OSD menu
     220           0 : void AP_RunCam::exit_osd()
     221             : {
     222             :     debug("exit osd(%d)\n", int(_state));
     223           0 :     _osd_option = OSDOption::EXIT;
     224           0 : }
     225             : 
     226             : // OSD control determined by camera options
     227           0 : void AP_RunCam::osd_option() {
     228             :     debug("osd option\n");
     229           0 :     _osd_option = OSDOption::OPTION;
     230           0 : }
     231             : 
     232             : // input update loop
     233         622 : void AP_RunCam::update()
     234             : {
     235         622 :     if (uart == nullptr || _cam_type.get() == int8_t(DeviceModel::Disabled)) {
     236         622 :         return;
     237             :     }
     238             : 
     239             :     // process any pending packets
     240           0 :     receive();
     241             : 
     242           0 :     uint32_t now = AP_HAL::millis();
     243           0 :     if ((now - _last_osd_update_ms) > RUNCAM_OSD_UPDATE_INTERVAL_MS) {
     244           0 :         update_osd();
     245           0 :         _last_osd_update_ms = now;
     246             :     }
     247             : }
     248             : 
     249             : // pre_arm_check - returns true if all pre-takeoff checks have completed successfully
     250          16 : bool AP_RunCam::pre_arm_check(char *failure_msg, const uint8_t failure_msg_len) const
     251             : {
     252             :     // if not enabled return true
     253          16 :     if (!uart) {
     254          16 :         return true;
     255             :     }
     256             : 
     257             :     // currently in the OSD menu, do not allow arming
     258           0 :     if (is_arming_prevented()) {
     259           0 :         hal.util->snprintf(failure_msg, failure_msg_len, "In OSD menu");
     260           0 :         return false;
     261             :     }
     262             : 
     263           0 :     if (!camera_ready()) {
     264           0 :         hal.util->snprintf(failure_msg, failure_msg_len, "Camera not ready");
     265           0 :         return false;
     266             :     }
     267             : 
     268             :     // if we got this far everything must be ok
     269           0 :     return true;
     270             : 
     271             : }
     272             : 
     273             : // OSD update loop
     274           0 : void AP_RunCam::update_osd()
     275             : {
     276           0 :     bool use_armed_state_machine = hal.util->get_soft_armed();
     277             : #if OSD_ENABLED
     278             :     // prevent runcam stick gestures interfering with osd stick gestures
     279           0 :     if (!use_armed_state_machine) {
     280           0 :         const AP_OSD* osd = AP::osd();
     281           0 :         if (osd != nullptr) {
     282           0 :             use_armed_state_machine = !osd->is_readonly_screen();
     283             :         }
     284             :     }
     285             : #endif
     286             :     // run a reduced state simulation process when armed
     287           0 :     if (use_armed_state_machine) {
     288           0 :         update_state_machine_armed();
     289           0 :         return;
     290             :     }
     291             : 
     292           0 :     update_state_machine_disarmed();
     293             : }
     294             : 
     295             : // update the state machine when armed or flying
     296           0 : void AP_RunCam::update_state_machine_armed()
     297             : {
     298           0 :     const uint32_t now = AP_HAL::millis();
     299           0 :     if ((now - _transition_start_ms) < _transition_timeout_ms) {
     300           0 :         return;
     301             :     }
     302             : 
     303           0 :     _transition_start_ms = now;
     304           0 :     _transition_timeout_ms = 0;
     305             : 
     306           0 :     switch (_state) {
     307           0 :     case State::READY:
     308           0 :         handle_ready(_video_recording == VideoOption::RECORDING && has_feature(Feature::RCDEVICE_PROTOCOL_FEATURE_START_RECORDING) ? Event::START_RECORDING : Event::NONE);
     309           0 :         break;
     310           0 :     case State::VIDEO_RECORDING:
     311           0 :         handle_recording(_video_recording == VideoOption::NOT_RECORDING && has_feature(Feature::RCDEVICE_PROTOCOL_FEATURE_START_RECORDING) ? Event::STOP_RECORDING : Event::NONE);
     312           0 :         break;
     313           0 :     case State::INITIALIZING:
     314             :     case State::INITIALIZED:
     315             :     case State::ENTERING_MENU:
     316             :     case State::IN_MENU:
     317             :     case State::EXITING_MENU:
     318           0 :         break;
     319             :     }
     320             : }
     321             : 
     322             : // update the state machine when disarmed
     323           0 : void AP_RunCam::update_state_machine_disarmed()
     324             : {
     325           0 :     const uint32_t now = AP_HAL::millis();
     326           0 :     if (_waiting_device_response || (now - _transition_start_ms) < _transition_timeout_ms) {
     327           0 :         _last_rc_event = Event::NONE;
     328           0 :         return;
     329             :     }
     330             : 
     331           0 :     _transition_start_ms = now;
     332           0 :     _transition_timeout_ms = 0;
     333             : 
     334           0 :     const Event ev = map_rc_input_to_event();
     335             :     // only take action on transitions
     336           0 :     if (ev == _last_rc_event && _state == _last_state && _osd_option == _last_osd_option
     337           0 :         && _last_in_menu == _in_menu && _last_video_recording == _video_recording) {
     338           0 :         return;
     339             :     }
     340             : 
     341             :     debug("update_state_machine_disarmed(%s)\n", event_names[int(ev)]);
     342             : 
     343           0 :     _last_rc_event = ev;
     344           0 :     _last_state = _state;
     345           0 :     _last_osd_option = _osd_option;
     346           0 :     _last_in_menu = _in_menu;
     347           0 :     _last_video_recording = _video_recording;
     348             : 
     349           0 :     switch (_state) {
     350           0 :     case State::INITIALIZING:
     351           0 :         break;
     352           0 :     case State::INITIALIZED:
     353           0 :         handle_initialized(ev);
     354           0 :         break;
     355           0 :     case State::READY:
     356           0 :         handle_ready(ev);
     357           0 :         break;
     358           0 :     case State::VIDEO_RECORDING:
     359           0 :         handle_recording(ev);
     360           0 :         break;
     361           0 :     case State::ENTERING_MENU:
     362           0 :         handle_in_menu(Event::ENTER_MENU);
     363           0 :         break;
     364           0 :     case State::IN_MENU:
     365           0 :         handle_in_menu(ev);
     366           0 :         break;
     367           0 :     case State::EXITING_MENU:
     368           0 :         handle_in_menu(Event::EXIT_MENU);
     369           0 :         break;
     370             :     }
     371             : }
     372             : 
     373             : // handle the initialized state
     374           0 : void AP_RunCam::handle_initialized(Event ev)
     375             : {
     376             :     // the camera should be configured to start with recording mode off by default
     377             :     // a recording change needs significantly extra time to process
     378           0 :     if (_video_recording == VideoOption::RECORDING && has_feature(Feature::RCDEVICE_PROTOCOL_FEATURE_START_RECORDING)) {
     379           0 :         if (!(_cam_control_option & uint8_t(ControlOption::VIDEO_RECORDING_AT_BOOT))) {
     380           0 :             simulate_camera_button(start_recording_command(), _mode_delay_ms * 2);
     381             :         }
     382           0 :         _state = State::VIDEO_RECORDING;
     383           0 :     } else if (_video_recording == VideoOption::NOT_RECORDING && has_feature(Feature::RCDEVICE_PROTOCOL_FEATURE_START_RECORDING)) {
     384           0 :         if (_cam_control_option & uint8_t(ControlOption::VIDEO_RECORDING_AT_BOOT)) {
     385           0 :             simulate_camera_button(stop_recording_command(), _mode_delay_ms * 2);
     386             :         }
     387           0 :         _state = State::READY;
     388             :     } else {
     389           0 :         _state = State::READY;
     390             :     }
     391             :     debug("device fully booted after %ums\n", unsigned(AP_HAL::millis()));
     392           0 : }
     393             : 
     394             : // handle the ready state
     395           0 : void AP_RunCam::handle_ready(Event ev)
     396             : {
     397           0 :     switch (ev) {
     398           0 :     case Event::ENTER_MENU:
     399             :     case Event::IN_MENU_ENTER:
     400             :     case Event::IN_MENU_RIGHT:
     401           0 :         if (ev == Event::ENTER_MENU || _cam_control_option & uint8_t(ControlOption::STICK_ROLL_RIGHT)) {
     402           0 :             _top_menu_pos = -1;
     403           0 :             _sub_menu_pos = 0;
     404           0 :             _state = State::ENTERING_MENU;
     405             :         }
     406           0 :         break;
     407           0 :     case Event::START_RECORDING:
     408           0 :         simulate_camera_button(start_recording_command(), _mode_delay_ms);
     409           0 :         _state = State::VIDEO_RECORDING;
     410           0 :         break;
     411           0 :     case Event::NONE:
     412             :     case Event::EXIT_MENU:
     413             :     case Event::IN_MENU_UP:
     414             :     case Event::IN_MENU_DOWN:
     415             :     case Event::IN_MENU_EXIT:
     416             :     case Event::BUTTON_RELEASE:
     417             :     case Event::STOP_RECORDING:
     418           0 :         break;
     419             :     }
     420           0 : }
     421             : 
     422             : // handle the recording state
     423           0 : void AP_RunCam::handle_recording(Event ev)
     424             : {
     425           0 :     switch (ev) {
     426           0 :     case Event::ENTER_MENU:
     427             :     case Event::IN_MENU_ENTER:
     428             :     case Event::IN_MENU_RIGHT:
     429           0 :         if (ev == Event::ENTER_MENU || _cam_control_option & uint8_t(ControlOption::STICK_ROLL_RIGHT)) {
     430           0 :             simulate_camera_button(stop_recording_command(), _mode_delay_ms);
     431           0 :             _top_menu_pos = -1;
     432           0 :             _sub_menu_pos = 0;
     433           0 :             _state = State::ENTERING_MENU;
     434             :         }
     435           0 :         break;
     436           0 :     case Event::STOP_RECORDING:
     437           0 :         simulate_camera_button(stop_recording_command(), _mode_delay_ms);
     438           0 :         _state = State::READY;
     439           0 :         break;
     440           0 :     case Event::NONE:
     441             :     case Event::EXIT_MENU:
     442             :     case Event::IN_MENU_UP:
     443             :     case Event::IN_MENU_DOWN:
     444             :     case Event::IN_MENU_EXIT:
     445             :     case Event::BUTTON_RELEASE:
     446             :     case Event::START_RECORDING:
     447           0 :         break;
     448             :     }
     449           0 : }
     450             : 
     451             : // handle the in_menu state
     452           0 : void AP_RunCam::handle_in_menu(Event ev)
     453             : {
     454           0 :     if (has_5_key_OSD()) {
     455           0 :         handle_5_key_simulation_process(ev);
     456           0 :     } else if (has_2_key_OSD()) {
     457             :         // otherwise the simpler 2 key OSD simulation, requires firmware 2.4.4 on the split micro
     458           0 :         handle_2_key_simulation_process(ev);
     459             :     }
     460           0 : }
     461             : 
     462             : // map rc input to an event
     463           0 : AP_RunCam::Event AP_RunCam::map_rc_input_to_event() const
     464             : {
     465           0 :     const RC_Channel::AuxSwitchPos throttle = rc().get_throttle_channel().get_stick_gesture_pos();
     466           0 :     const RC_Channel::AuxSwitchPos yaw = rc().get_yaw_channel().get_stick_gesture_pos();
     467           0 :     const RC_Channel::AuxSwitchPos roll = rc().get_roll_channel().get_stick_gesture_pos();
     468           0 :     const RC_Channel::AuxSwitchPos pitch = rc().get_pitch_channel().get_stick_gesture_pos();
     469             : 
     470           0 :     Event result = Event::NONE;
     471             : 
     472           0 :     if (_button_pressed != ButtonState::NONE) {
     473           0 :         if (_button_pressed == ButtonState::PRESSED && yaw == RC_Channel::AuxSwitchPos::MIDDLE && pitch == RC_Channel::AuxSwitchPos::MIDDLE && roll == RC_Channel::AuxSwitchPos::MIDDLE) {
     474           0 :             result = Event::BUTTON_RELEASE;
     475             :         } else {
     476           0 :             result = Event::NONE; // still waiting to be released
     477             :         }
     478           0 :     } else if (throttle == RC_Channel::AuxSwitchPos::MIDDLE && yaw == RC_Channel::AuxSwitchPos::LOW
     479           0 :         && pitch == RC_Channel::AuxSwitchPos::MIDDLE && roll == RC_Channel::AuxSwitchPos::MIDDLE
     480             :         // don't allow an action close to arming unless the user had configured it or arming is not possible
     481             :         // but don't prevent the 5-Key control actually working
     482           0 :         && (_cam_control_option & uint8_t(ControlOption::STICK_YAW_RIGHT) || is_arming_prevented())) {
     483           0 :         result = Event::EXIT_MENU;
     484           0 :     } else if (throttle == RC_Channel::AuxSwitchPos::MIDDLE && yaw == RC_Channel::AuxSwitchPos::HIGH
     485           0 :         && pitch == RC_Channel::AuxSwitchPos::MIDDLE && roll == RC_Channel::AuxSwitchPos::MIDDLE
     486           0 :         && (_cam_control_option & uint8_t(ControlOption::STICK_YAW_RIGHT) || is_arming_prevented())) {
     487           0 :         result = Event::ENTER_MENU;
     488           0 :     } else if (roll == RC_Channel::AuxSwitchPos::LOW) {
     489           0 :         result = Event::IN_MENU_EXIT;
     490           0 :     } else if (yaw == RC_Channel::AuxSwitchPos::MIDDLE && pitch == RC_Channel::AuxSwitchPos::MIDDLE && roll == RC_Channel::AuxSwitchPos::HIGH) {
     491           0 :         if (has_5_key_OSD()) {
     492           0 :             result = Event::IN_MENU_RIGHT;
     493             :         } else {
     494           0 :             result = Event::IN_MENU_ENTER;
     495             :         }
     496           0 :     } else if (pitch == RC_Channel::AuxSwitchPos::LOW) {
     497           0 :         result = Event::IN_MENU_UP;
     498           0 :     } else if (pitch == RC_Channel::AuxSwitchPos::HIGH) {
     499           0 :         result = Event::IN_MENU_DOWN;
     500           0 :     } else if (_video_recording != _last_video_recording) {
     501           0 :         switch (_video_recording) {
     502           0 :             case VideoOption::NOT_RECORDING:
     503           0 :                 result = Event::STOP_RECORDING;
     504           0 :                 break;
     505           0 :             case VideoOption::RECORDING:
     506           0 :                 result = Event::START_RECORDING;
     507           0 :                 break;
     508             :         }
     509           0 :     } else if (_osd_option == _last_osd_option) {
     510             :         // OSD option has not changed so assume stick re-centering
     511           0 :         result = Event::NONE;
     512           0 :     } else if (_osd_option == OSDOption::ENTER
     513           0 :         && _cam_control_option & uint8_t(ControlOption::TWO_POS_SWITCH)) {
     514           0 :         result = Event::ENTER_MENU;
     515           0 :     } else if ((_osd_option == OSDOption::OPTION || _osd_option == OSDOption::ENTER)
     516           0 :         && _cam_control_option & uint8_t(ControlOption::THREE_POS_SWITCH)) {
     517           0 :         result = Event::ENTER_MENU;
     518           0 :     } else if (_osd_option == OSDOption::EXIT
     519           0 :         && _cam_control_option & uint8_t(ControlOption::TWO_POS_SWITCH)) {
     520           0 :         result = Event::EXIT_MENU;
     521           0 :     } else if ((_osd_option == OSDOption::NO_OPTION || _osd_option == OSDOption::EXIT)
     522           0 :         && _cam_control_option & uint8_t(ControlOption::THREE_POS_SWITCH)) {
     523           0 :         result = Event::EXIT_MENU;
     524             :     } else {
     525             :         debug("map_rc_input_to_event(): nothing selected\n");
     526             :     }
     527           0 :     return result;
     528             : }
     529             : 
     530             : // run the 2-key OSD simulation process, this involves using the power and mode (wifi) buttons
     531             : // to cycle through options. unfortunately these are one-way requests so we need to use delays
     532             : // to make sure that the camera obeys
     533           0 : void AP_RunCam::handle_2_key_simulation_process(Event ev)
     534             : {
     535             :     debug("%s,M:%d,V:%d,O:%d\n", event_names[int(ev)], _in_menu, int(_video_recording), int(_osd_option));
     536             : 
     537           0 :     switch (ev) {
     538           0 :     case Event::ENTER_MENU:
     539           0 :         if (_in_menu <= 0) {
     540           0 :             _in_menu++;
     541           0 :             simulate_camera_button(ControlOperation::RCDEVICE_PROTOCOL_CHANGE_MODE, _mode_delay_ms);
     542           0 :             if (_in_menu > 0) {
     543             :                 // turn off built-in OSD so that the runcam OSD is visible
     544           0 :                 disable_osd();
     545           0 :                 _state = State::IN_MENU;
     546             :             } else {
     547           0 :                 _state = State::ENTERING_MENU;
     548             :             }
     549             :         }
     550           0 :         break;
     551             : 
     552           0 :     case Event::EXIT_MENU:
     553             :         // keep changing mode until we are fully out of the menu
     554           0 :         if (_in_menu > 0) {
     555           0 :             _in_menu--;
     556           0 :             simulate_camera_button(ControlOperation::RCDEVICE_PROTOCOL_CHANGE_MODE, _mode_delay_ms);
     557           0 :             _state = State::EXITING_MENU;
     558             :         } else {
     559           0 :             exit_2_key_osd_menu();
     560             :         }
     561           0 :         break;
     562             : 
     563           0 :     case Event::IN_MENU_ENTER:
     564             :         // in a sub-menu and save-and-exit was selected
     565           0 :         if (_in_menu > 1 && get_top_menu_length() > 0 && _sub_menu_pos == (get_sub_menu_length(_top_menu_pos) - 1) && DeviceModel(_cam_type.get()) != DeviceModel::Run24k) {
     566           0 :             simulate_camera_button(ControlOperation::RCDEVICE_PROTOCOL_SIMULATE_WIFI_BTN, _button_delay_ms);
     567           0 :             _sub_menu_pos = 0;
     568           0 :             _in_menu--;
     569             :         // in the top-menu and save-and-exit was selected
     570           0 :         } else if (_in_menu == 1 && get_top_menu_length() > 0 && _top_menu_pos == (get_top_menu_length() - 1) && DeviceModel(_cam_type.get()) != DeviceModel::Run24k) {
     571           0 :             simulate_camera_button(ControlOperation::RCDEVICE_PROTOCOL_SIMULATE_WIFI_BTN, _mode_delay_ms);
     572           0 :             _in_menu--;
     573           0 :             _state = State::EXITING_MENU;
     574           0 :         } else if (_top_menu_pos >= 0 && get_sub_menu_length(_top_menu_pos) > 0) {
     575           0 :             simulate_camera_button(ControlOperation::RCDEVICE_PROTOCOL_SIMULATE_WIFI_BTN, _button_delay_ms);
     576           0 :             _in_menu = MIN(_in_menu + 1, RUNCAM_OSD_MENU_DEPTH);
     577             :         }
     578           0 :         break;
     579             : 
     580           0 :     case Event::IN_MENU_UP:
     581             :     case Event::IN_MENU_DOWN:
     582           0 :         simulate_camera_button(ControlOperation::RCDEVICE_PROTOCOL_SIMULATE_POWER_BTN, _button_delay_ms);    // move to setting
     583           0 :         if (_in_menu > 1) {
     584             :             // in a sub-menu, keep track of the selected position
     585           0 :             _sub_menu_pos = (_sub_menu_pos + 1) % get_sub_menu_length(_top_menu_pos);
     586             :         } else {
     587             :             // in the top-menu, keep track of the selected position
     588           0 :             _top_menu_pos = (_top_menu_pos + 1) % get_top_menu_length();
     589             :         }
     590           0 :         break;
     591             : 
     592           0 :     case Event::IN_MENU_EXIT:
     593             :         // if we are in a sub-menu this will move us out, if we are in the root menu this will
     594             :         // exit causing the state machine to get out of sync. the OSD menu hierarchy is consistently
     595             :         // 2 deep so we can count and be reasonably confident of where we are.
     596             :         // the only exception is if someone hits save and exit on the root menu - then we are lost.
     597           0 :         if (_in_menu > 0) {
     598           0 :             _in_menu--;
     599           0 :             _sub_menu_pos = 0;
     600           0 :             simulate_camera_button(ControlOperation::RCDEVICE_PROTOCOL_CHANGE_MODE, _mode_delay_ms);     // move up/out a menu
     601             :         }
     602             :         // no longer in the menu so trigger the OSD re-enablement
     603           0 :         if (_in_menu == 0) {
     604           0 :             _in_menu = _menu_enter_level;
     605           0 :             _state = State::EXITING_MENU;
     606             :         }
     607           0 :         break;
     608             : 
     609           0 :     case Event::NONE:
     610             :     case Event::IN_MENU_RIGHT:
     611             :     case Event::BUTTON_RELEASE:
     612             :     case Event::START_RECORDING:
     613             :     case Event::STOP_RECORDING:
     614           0 :         break;
     615             :     }
     616           0 : }
     617             : 
     618             : // exit the 2 key OSD menu
     619           0 : void AP_RunCam::exit_2_key_osd_menu()
     620             : {
     621           0 :     _in_menu = _menu_enter_level;
     622             : 
     623             :     // turn built-in OSD back on
     624           0 :     enable_osd();
     625             : 
     626           0 :     if (_video_recording == VideoOption::RECORDING && has_feature(Feature::RCDEVICE_PROTOCOL_FEATURE_START_RECORDING)) {
     627           0 :         simulate_camera_button(start_recording_command(), _mode_delay_ms);
     628           0 :         _state = State::VIDEO_RECORDING;
     629             :     } else {
     630           0 :         _state = State::READY;
     631             :     }
     632           0 : }
     633             : 
     634             : // run the 5-key OSD simulation process
     635           0 : void AP_RunCam::handle_5_key_simulation_process(Event ev) 
     636             : {
     637             :     debug("%s,M:%d,B:%d,O:%d\n", event_names[int(ev)], _in_menu, int(_button_pressed), int(_osd_option));
     638             : 
     639           0 :     switch (ev) {
     640           0 :     case Event::BUTTON_RELEASE:
     641           0 :         send_5_key_OSD_cable_simulation_event(ev);
     642           0 :         break;
     643             : 
     644           0 :     case Event::ENTER_MENU:
     645           0 :         if (_in_menu == 0) {
     646             :             // turn off built-in OSD so that the runcam OSD is visible
     647           0 :             disable_osd();
     648           0 :             send_5_key_OSD_cable_simulation_event(ev);
     649           0 :             _in_menu = 1;
     650             :         } else {
     651           0 :             send_5_key_OSD_cable_simulation_event(Event::IN_MENU_ENTER);
     652             :         }
     653           0 :         break;
     654             : 
     655           0 :     case Event::EXIT_MENU:
     656           0 :         if (_in_menu > 0) {
     657             :             // turn built-in OSD back on
     658           0 :             enable_osd();
     659           0 :             send_5_key_OSD_cable_simulation_event(Event::EXIT_MENU);
     660           0 :             _in_menu = 0;
     661             :         }
     662           0 :         break;
     663             : 
     664           0 :     case Event::NONE:
     665           0 :         break;
     666             : 
     667           0 :     case Event::IN_MENU_EXIT:
     668             :     case Event::IN_MENU_RIGHT:
     669             :     case Event::IN_MENU_ENTER:
     670             :     case Event::IN_MENU_UP:
     671             :     case Event::IN_MENU_DOWN:
     672             :     case Event::START_RECORDING:
     673             :     case Event::STOP_RECORDING:
     674           0 :         send_5_key_OSD_cable_simulation_event(ev);
     675           0 :         break;
     676             :     }
     677           0 : }
     678             : 
     679             : // handle a response
     680           0 : void AP_RunCam::handle_5_key_simulation_response(const Request& request)
     681             : {
     682             :     debug("response for command %d result: %d\n", int(request._command), int(request._result));
     683           0 :     if (request._result != RequestStatus::SUCCESS) {
     684           0 :         simulation_OSD_cable_failed(request);
     685           0 :         _button_pressed = ButtonState::NONE;
     686           0 :         _waiting_device_response = false;
     687           0 :         return;
     688             :     }
     689             : 
     690           0 :     switch (request._command) {
     691           0 :     case Command::RCDEVICE_PROTOCOL_COMMAND_5KEY_SIMULATION_RELEASE:
     692           0 :         _button_pressed = ButtonState::NONE;
     693           0 :         break;
     694           0 :     case Command::RCDEVICE_PROTOCOL_COMMAND_5KEY_CONNECTION:
     695             :     {
     696             :         // the high 4 bits is the operationID that we sent
     697             :         // the low 4 bits is the result code
     698           0 :         const ConnectionOperation operationID = ConnectionOperation(request._param);
     699           0 :         const uint8_t errorCode = (request._recv_buf[1] & 0x0F);
     700             :         switch (operationID) {
     701           0 :         case ConnectionOperation::RCDEVICE_PROTOCOL_5KEY_FUNCTION_OPEN:
     702           0 :             if (errorCode > 0) {
     703           0 :                 _state = State::IN_MENU;
     704             :             }
     705           0 :             break;
     706           0 :         case ConnectionOperation::RCDEVICE_PROTOCOL_5KEY_FUNCTION_CLOSE:
     707           0 :             if (errorCode > 0) {
     708           0 :                 _state = State::READY;
     709             :             }
     710           0 :             break;
     711             :         }
     712           0 :         break;
     713             :     }
     714           0 :     case Command::RCDEVICE_PROTOCOL_COMMAND_5KEY_SIMULATION_PRESS:
     715             :     case Command::RCDEVICE_PROTOCOL_COMMAND_GET_DEVICE_INFO:
     716             :     case Command::RCDEVICE_PROTOCOL_COMMAND_CAMERA_CONTROL:
     717             :     case Command::COMMAND_NONE:
     718           0 :         break;
     719             :     }
     720             : 
     721           0 :     _waiting_device_response = false;
     722             : }
     723             : 
     724             : // command to start recording
     725           0 : AP_RunCam::ControlOperation AP_RunCam::start_recording_command() const {
     726           0 :     if (DeviceModel(_cam_type.get()) == DeviceModel::Split4k || DeviceModel(_cam_type.get()) == DeviceModel::Hybrid || DeviceModel(_cam_type.get()) == DeviceModel::Run24k) {
     727           0 :         return ControlOperation::RCDEVICE_PROTOCOL_SIMULATE_POWER_BTN;
     728             :     } else {
     729           0 :         return ControlOperation::RCDEVICE_PROTOCOL_CHANGE_START_RECORDING;
     730             :     }
     731             : }
     732             : 
     733             : // command to stop recording
     734           0 : AP_RunCam::ControlOperation AP_RunCam::stop_recording_command() const {
     735           0 :     if (DeviceModel(_cam_type.get()) == DeviceModel::Split4k || DeviceModel(_cam_type.get()) == DeviceModel::Hybrid || DeviceModel(_cam_type.get()) == DeviceModel::Run24k) {
     736           0 :         return ControlOperation::RCDEVICE_PROTOCOL_SIMULATE_POWER_BTN;
     737             :     } else {
     738           0 :         return ControlOperation::RCDEVICE_PROTOCOL_CHANGE_STOP_RECORDING;
     739             :     }
     740             : }
     741             : 
     742             : // process a response from the serial port
     743           0 : void AP_RunCam::receive()
     744             : {
     745           0 :     if (!uart) {
     746           0 :         return;
     747             :     }
     748             :     // process any pending request at least once-per cycle, regardless of available bytes
     749           0 :     if (!request_pending(AP_HAL::millis())) {
     750           0 :         return;
     751             :     }
     752             : 
     753           0 :     uint32_t avail = MIN(uart->available(), (uint32_t)RUNCAM_MAX_PACKET_SIZE);
     754             : 
     755           0 :     for (uint32_t i = 0; i < avail; i++) {
     756             : 
     757           0 :         if (!request_pending(AP_HAL::millis())) {
     758           0 :              return;
     759             :         }
     760             : 
     761           0 :         const uint8_t c = uart->read();
     762           0 :         if (_pending_request._recv_response_length == 0) {
     763             :             // Only start receiving packet when we found a header
     764           0 :             if (c != RUNCAM_HEADER) {
     765           0 :                 continue;
     766             :             }
     767             :         }
     768             : 
     769           0 :         _pending_request._recv_buf[_pending_request._recv_response_length] = c;
     770           0 :         _pending_request._recv_response_length += 1;
     771             : 
     772             :         // if data received done, trigger callback to parse response data, and update RUNCAM state
     773           0 :         if (_pending_request._recv_response_length == _pending_request._expected_response_length) {
     774           0 :             uint8_t crc = _pending_request.get_crc();
     775             : 
     776           0 :             _pending_request._result = (crc == 0) ? RequestStatus::SUCCESS : RequestStatus::INCORRECT_CRC;
     777             : 
     778             :             debug("received response for command %d\n", int(_pending_request._command));
     779           0 :             _pending_request.parse_response();
     780             :             // we no longer have a pending request
     781           0 :             _pending_request._result = RequestStatus::NONE;
     782             :         }
     783             :     }
     784             : }
     785             : 
     786             : // every time we send a packet to device and want to get a response
     787             : // it's better to clear the rx buffer before the sending the packet
     788             : // otherwise useless data in rx buffer will cause the response decoding
     789             : // to fail
     790           0 : void AP_RunCam::drain()
     791             : {
     792           0 :     if (!uart) {
     793           0 :         return;
     794             :     }
     795             : 
     796           0 :     uart->discard_input();
     797             : }
     798             : 
     799             : // start the uart if we have one
     800           0 : void AP_RunCam::start_uart()
     801             : {
     802             :     // 8N1 communication
     803           0 :     uart->configure_parity(0);
     804           0 :     uart->set_stop_bits(1);
     805           0 :     uart->set_flow_control(AP_HAL::UARTDriver::FLOW_CONTROL_DISABLE);
     806           0 :     uart->set_options(uart->get_options() | AP_HAL::UARTDriver::OPTION_NODMA_TX | AP_HAL::UARTDriver::OPTION_NODMA_RX);
     807           0 :     uart->begin(115200, 10, 10);
     808           0 :     uart->discard_input();
     809           0 : }
     810             : 
     811             : // get the device info (firmware version, protocol version and features)
     812           0 : void AP_RunCam::get_device_info()
     813             : {
     814           0 :     send_request_and_waiting_response(Command::RCDEVICE_PROTOCOL_COMMAND_GET_DEVICE_INFO, 0, RUNCAM_INIT_INTERVAL_MS * 4,
     815             :         UINT16_MAX, FUNCTOR_BIND_MEMBER(&AP_RunCam::parse_device_info, void, const Request&));
     816           0 : }
     817             : 
     818             : // map a Event to a SimulationOperation
     819           0 : AP_RunCam::SimulationOperation AP_RunCam::map_key_to_protocol_operation(const Event key) const
     820             : {
     821           0 :     SimulationOperation operation = SimulationOperation::SIMULATION_NONE;
     822           0 :     switch (key) {
     823           0 :     case Event::IN_MENU_EXIT:
     824           0 :         operation = SimulationOperation::RCDEVICE_PROTOCOL_5KEY_SIMULATION_LEFT;
     825           0 :         break;
     826           0 :     case Event::IN_MENU_UP:
     827           0 :         operation = SimulationOperation::RCDEVICE_PROTOCOL_5KEY_SIMULATION_UP;
     828           0 :         break;
     829           0 :     case Event::IN_MENU_RIGHT:
     830           0 :         operation = SimulationOperation::RCDEVICE_PROTOCOL_5KEY_SIMULATION_RIGHT;
     831           0 :         break;
     832           0 :     case Event::IN_MENU_DOWN:
     833           0 :         operation = SimulationOperation::RCDEVICE_PROTOCOL_5KEY_SIMULATION_DOWN;
     834           0 :         break;
     835           0 :     case Event::IN_MENU_ENTER:
     836           0 :         operation = SimulationOperation::RCDEVICE_PROTOCOL_5KEY_SIMULATION_SET;
     837           0 :         break;
     838           0 :     case Event::BUTTON_RELEASE:
     839             :     case Event::NONE:
     840             :     case Event::ENTER_MENU:
     841             :     case Event::EXIT_MENU:
     842             :     case Event::STOP_RECORDING:
     843             :     case Event::START_RECORDING:
     844           0 :         break;
     845             :     }
     846           0 :     return operation;
     847             : }
     848             : 
     849             : // send an event
     850           0 : void AP_RunCam::send_5_key_OSD_cable_simulation_event(const Event key, const uint32_t transition_timeout)
     851             : {
     852             :     debug("OSD cable simulation event %s\n", event_names[int(key)]);
     853           0 :     _waiting_device_response = true;
     854             :     // although we can control press/release, this causes the state machine to behave in the same way
     855             :     // as the 2-key process
     856           0 :     _transition_timeout_ms = transition_timeout;
     857             : 
     858           0 :     switch (key) {
     859           0 :     case Event::ENTER_MENU:
     860           0 :         open_5_key_OSD_cable_connection(FUNCTOR_BIND_MEMBER(&AP_RunCam::handle_5_key_simulation_response, void, const Request&));
     861           0 :         break;
     862           0 :     case Event::EXIT_MENU:
     863           0 :         close_5_key_OSD_cable_connection(FUNCTOR_BIND_MEMBER(&AP_RunCam::handle_5_key_simulation_response, void, const Request&));
     864           0 :         break;
     865           0 :     case Event::IN_MENU_UP:
     866             :     case Event::IN_MENU_RIGHT:
     867             :     case Event::IN_MENU_DOWN:
     868             :     case Event::IN_MENU_ENTER:
     869             :     case Event::IN_MENU_EXIT:
     870           0 :         simulate_5_key_OSD_cable_button_press(map_key_to_protocol_operation(key), FUNCTOR_BIND_MEMBER(&AP_RunCam::handle_5_key_simulation_response, void, const Request&));
     871           0 :         break;
     872           0 :     case Event::BUTTON_RELEASE:
     873           0 :         simulate_5_key_OSD_cable_button_release(FUNCTOR_BIND_MEMBER(&AP_RunCam::handle_5_key_simulation_response, void, const Request&));
     874           0 :         break;
     875           0 :     case Event::STOP_RECORDING:
     876             :     case Event::START_RECORDING:
     877             :     case Event::NONE:
     878           0 :         break;
     879             :     }
     880           0 : }
     881             : 
     882             : // every time we run the OSD menu simulation it's necessary to open the connection
     883           0 : void AP_RunCam::open_5_key_OSD_cable_connection(parse_func_t parseFunc)
     884             : {
     885           0 :     send_request_and_waiting_response(Command::RCDEVICE_PROTOCOL_COMMAND_5KEY_CONNECTION,
     886             :         uint8_t(ConnectionOperation::RCDEVICE_PROTOCOL_5KEY_FUNCTION_OPEN), 400, 2, parseFunc);
     887           0 : }
     888             : 
     889             : // every time we exit the OSD menu simulation it's necessary to close the connection
     890           0 : void AP_RunCam::close_5_key_OSD_cable_connection(parse_func_t parseFunc)
     891             : {
     892           0 :     send_request_and_waiting_response(Command::RCDEVICE_PROTOCOL_COMMAND_5KEY_CONNECTION,
     893             :         uint8_t(ConnectionOperation::RCDEVICE_PROTOCOL_5KEY_FUNCTION_CLOSE), 400, 2, parseFunc);
     894           0 : }
     895             : 
     896             : // simulate button press event of 5 key OSD cable with special button
     897           0 : void AP_RunCam::simulate_5_key_OSD_cable_button_press(const SimulationOperation operation, parse_func_t parseFunc)
     898             : {
     899           0 :     if (operation == SimulationOperation::SIMULATION_NONE) {
     900           0 :         return;
     901             :     }
     902             : 
     903           0 :     _button_pressed = ButtonState::PRESSED;
     904             : 
     905           0 :     send_request_and_waiting_response(Command::RCDEVICE_PROTOCOL_COMMAND_5KEY_SIMULATION_PRESS, uint8_t(operation), 400, 2, parseFunc);
     906             : }
     907             : 
     908             : // simulate button release event of 5 key OSD cable
     909           0 : void AP_RunCam::simulate_5_key_OSD_cable_button_release(parse_func_t parseFunc)
     910             : {
     911           0 :     _button_pressed = ButtonState::RELEASED;
     912             : 
     913           0 :     send_request_and_waiting_response(Command::RCDEVICE_PROTOCOL_COMMAND_5KEY_SIMULATION_RELEASE,
     914             :         uint8_t(SimulationOperation::SIMULATION_NONE), 400, 2, parseFunc);
     915           0 : }
     916             : 
     917             : // send a RunCam request and register a response to be processed
     918           0 : void AP_RunCam::send_request_and_waiting_response(Command commandID, uint8_t param,
     919             :     uint32_t timeout, uint16_t maxRetryTimes, parse_func_t parserFunc)
     920             : {
     921           0 :     drain();
     922             : 
     923           0 :     _pending_request = Request(this, commandID, param, timeout, maxRetryTimes, parserFunc);
     924             :     debug("sending command: %d, op: %d\n", int(commandID), int(param));
     925             :     // send packet
     926           0 :     send_packet(commandID, param);
     927           0 : }
     928             : 
     929             : // send a packet to the serial port
     930           0 : void AP_RunCam::send_packet(Command command, uint8_t param)
     931             : {
     932             :     // is this device open?
     933           0 :     if (!uart) {
     934           0 :         return;
     935             :     }
     936             : 
     937             :     uint8_t buffer[4];
     938             : 
     939           0 :     bool have_param = param > 0 || command == Command::RCDEVICE_PROTOCOL_COMMAND_CAMERA_CONTROL;
     940           0 :     uint8_t buffer_len = have_param ? 4 : 3;
     941             : 
     942           0 :     buffer[0] = RUNCAM_HEADER;
     943           0 :     buffer[1] = uint8_t(command);
     944           0 :     if (have_param) {
     945           0 :         buffer[2] = param;
     946             :     }
     947             : 
     948           0 :     uint8_t crc = 0;
     949           0 :     for (uint8_t i = 0; i < buffer_len - 1; i++) {
     950           0 :         crc = crc8_dvb_s2(crc, buffer[i]);
     951             :     }
     952             : 
     953           0 :     buffer[buffer_len - 1] = crc;
     954             : 
     955             :     // send data if possible
     956           0 :     uart->write(buffer, buffer_len);
     957           0 :     uart->flush();
     958             : }
     959             : 
     960             : // handle a device info response
     961           0 : void AP_RunCam::parse_device_info(const Request& request)
     962             : {
     963           0 :     _protocol_version = ProtocolVersion(request._recv_buf[1]);
     964             : 
     965           0 :     uint8_t featureLowBits = request._recv_buf[2];
     966           0 :     uint8_t featureHighBits = request._recv_buf[3];
     967           0 :     if (!has_feature(Feature::FEATURES_OVERRIDE)) {
     968           0 :         _features.set((featureHighBits << 8) | featureLowBits);
     969             :     }
     970           0 :     if (_features > 0) {
     971           0 :         _state = State::INITIALIZED;
     972           0 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "RunCam initialized, features 0x%04X, %d-key OSD", _features.get(),
     973             :             has_5_key_OSD() ? 5 : has_2_key_OSD() ? 2 : 0);
     974             :     } else {
     975             :         // nothing as as nothing does
     976           0 :         GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "RunCam device not found");
     977             :     }
     978             :     debug("RunCam: initialized state: video: %d, osd: %d, cam: %d\n", int(_video_recording), int(_osd_option), int(_cam_control_option));
     979           0 : }
     980             : 
     981             : // wait for the RunCam device to be fully ready
     982           0 : bool AP_RunCam::camera_ready() const
     983             : {
     984           0 :     if (_state != State::INITIALIZING && _state != State::INITIALIZED) {
     985           0 :         return true;
     986             :     }
     987           0 :     return false;
     988             : }
     989             : 
     990             : // error handler for OSD simulation
     991           0 : void AP_RunCam::simulation_OSD_cable_failed(const Request& request)
     992             : {
     993           0 :     _waiting_device_response = false;
     994           0 :     if (request._command == Command::RCDEVICE_PROTOCOL_COMMAND_5KEY_CONNECTION) {
     995           0 :         uint8_t operationID = request._param;
     996           0 :         if (operationID == uint8_t(ConnectionOperation::RCDEVICE_PROTOCOL_5KEY_FUNCTION_CLOSE)) {
     997           0 :             return;
     998             :         }
     999             :     }
    1000             : }
    1001             : 
    1002             : // process all of the pending responses, retrying as necessary
    1003           0 : bool AP_RunCam::request_pending(uint32_t now)
    1004             : {
    1005           0 :     if (_pending_request._result == RequestStatus::NONE) {
    1006           0 :         return false;
    1007             :     }
    1008             : 
    1009           0 :     if (_pending_request._request_timestamp_ms != 0 && (now - _pending_request._request_timestamp_ms) < _pending_request._timeout_ms) {
    1010             :         // request still in play
    1011           0 :         return true;
    1012             :     }
    1013             : 
    1014           0 :     if (_pending_request._max_retry_times > 0) {
    1015             :         // request timed out, so resend
    1016             :         debug("retrying[%d] command 0x%X, op 0x%X\n", int(_pending_request._max_retry_times), int(_pending_request._command), int(_pending_request._param));
    1017           0 :         start_uart();
    1018           0 :         _pending_request._device->send_packet(_pending_request._command, _pending_request._param);
    1019           0 :         _pending_request._recv_response_length = 0;
    1020           0 :         _pending_request._request_timestamp_ms = now;
    1021           0 :         _pending_request._max_retry_times -= 1;
    1022             : 
    1023           0 :         return false;
    1024             :     }
    1025             :     debug("timeout command 0x%X, op 0x%X\n", int(_pending_request._command), int(_pending_request._param));
    1026             :     // too many retries, fail the request
    1027           0 :     _pending_request._result = RequestStatus::TIMEOUT;
    1028           0 :     _pending_request.parse_response();
    1029           0 :     _pending_request._result = RequestStatus::NONE;
    1030             : 
    1031           0 :     return false;
    1032             : }
    1033             : 
    1034             : // constructor for a response structure
    1035           0 : AP_RunCam::Request::Request(AP_RunCam* device, Command commandID, uint8_t param,
    1036           0 :     uint32_t timeout, uint16_t maxRetryTimes, parse_func_t parserFunc)
    1037           0 :     : _recv_buf(device->_recv_buf),
    1038           0 :     _device(device),
    1039           0 :     _command(commandID),
    1040           0 :     _param(param),
    1041           0 :     _recv_response_length(0),
    1042           0 :     _timeout_ms(timeout),
    1043           0 :     _max_retry_times(maxRetryTimes),
    1044           0 :     _parser_func(parserFunc),
    1045           0 :     _result(RequestStatus::PENDING)
    1046             : {
    1047           0 :     _request_timestamp_ms = AP_HAL::millis();
    1048           0 :     _expected_response_length = get_expected_response_length(commandID);
    1049           0 : }
    1050             : 
    1051           0 : uint8_t AP_RunCam::Request::get_crc() const
    1052             : {
    1053           0 :     uint8_t crc = 0;
    1054           0 :     for (int i = 0; i < _recv_response_length; i++) {
    1055           0 :         crc = crc8_dvb_s2(crc, _recv_buf[i]);
    1056             :     }
    1057           0 :     return crc;
    1058             : }
    1059             : 
    1060             : // get the length of a response
    1061           0 : uint8_t AP_RunCam::Request::get_expected_response_length(const Command command) const
    1062             : {
    1063           0 :     for (uint16_t i = 0; i < RUNCAM_NUM_EXPECTED_RESPONSES; i++) {
    1064           0 :         if (_expected_responses_length[i].command == command) {
    1065           0 :             return _expected_responses_length[i].reponse_length;
    1066             :         }
    1067             :     }
    1068             : 
    1069           0 :     return 0;
    1070             : }
    1071             : 
    1072             : // AP_Camera API
    1073             : 
    1074             : // return true if healthy
    1075           0 : bool AP_RunCam::healthy() const
    1076             : {
    1077           0 :     return camera_ready();
    1078             : }
    1079             : 
    1080             : // momentary switch to change camera between picture and video modes
    1081           0 : void AP_RunCam::cam_mode_toggle()
    1082             : {
    1083             : 
    1084           0 : }
    1085             : 
    1086             : // entry point to actually take a picture.  returns true on success
    1087           0 : bool AP_RunCam::trigger_pic()
    1088             : {
    1089           0 :     return false;
    1090             : }
    1091             : 
    1092             : // send camera information message to GCS
    1093           0 : void AP_RunCam::send_camera_information(mavlink_channel_t chan) const
    1094             : {
    1095             :     // exit immediately if not initialised
    1096           0 :     if (!camera_ready() || _cam_type.get() <= 0 || _cam_type.get() > int8_t(ARRAY_SIZE(_models))) {
    1097           0 :         return;
    1098             :     }
    1099             : 
    1100             :     static const uint8_t vendor_name[32] = "RunCam";
    1101           0 :     uint8_t model_name[32] {};
    1102           0 :     strncpy((char *)model_name, _models[_cam_type.get()-1], MIN(sizeof(model_name), sizeof(_models[_cam_type.get()-1])));
    1103           0 :     const char cam_definition_uri[140] {};
    1104             : 
    1105             :     // capability flags
    1106           0 :     uint32_t flags = 0;
    1107             : 
    1108           0 :     if (has_feature(Feature::RCDEVICE_PROTOCOL_FEATURE_START_RECORDING)) {
    1109           0 :         flags = CAMERA_CAP_FLAGS_CAPTURE_VIDEO;
    1110             :     }
    1111             : 
    1112           0 :     if (has_feature(Feature::RCDEVICE_PROTOCOL_FEATURE_CHANGE_MODE)) {
    1113           0 :         flags |= CAMERA_CAP_FLAGS_CAPTURE_IMAGE;
    1114             :     }
    1115             : 
    1116             :     // send CAMERA_INFORMATION message
    1117           0 :     mavlink_msg_camera_information_send(
    1118             :         chan,
    1119             :         AP_HAL::millis(),       // time_boot_ms
    1120             :         vendor_name,            // vendor_name uint8_t[32]
    1121             :         model_name,             // model_name uint8_t[32]
    1122             :         0,                      // firmware version uint32_t
    1123             :         NaNf,                   // focal_length float (mm)
    1124             :         NaNf,                   // sensor_size_h float (mm)
    1125             :         NaNf,                   // sensor_size_v float (mm)
    1126             :         0,                      // resolution_h uint16_t (pix)
    1127             :         0,                      // resolution_v uint16_t (pix)
    1128             :         0,                      // lens_id uint8_t
    1129             :         flags,                  // flags uint32_t (CAMERA_CAP_FLAGS)
    1130             :         0,                      // cam_definition_version uint16_t
    1131             :         cam_definition_uri,     // cam_definition_uri char[140]
    1132           0 :         _instance + 1);         // gimbal_device_id uint8_t
    1133             : }
    1134             : 
    1135             : // send camera settings message to GCS
    1136           0 : void AP_RunCam::send_camera_settings(mavlink_channel_t chan) const
    1137             : {
    1138             :     // exit immediately if not initialised
    1139           0 :     if (!camera_ready()) {
    1140           0 :         return;
    1141             :     }
    1142             : 
    1143             :     // send CAMERA_SETTINGS message
    1144           0 :     mavlink_msg_camera_settings_send(
    1145             :         chan,
    1146             :         AP_HAL::millis(),   // time_boot_ms
    1147           0 :         _video_recording == VideoOption::RECORDING ? CAMERA_MODE_VIDEO : CAMERA_MODE_IMAGE, // camera mode (0:image, 1:video, 2:image survey)
    1148             :         NaNf,         // zoomLevel float, percentage from 0 to 100, NaN if unknown
    1149             :         NaNf);              // focusLevel float, percentage from 0 to 100, NaN if unknown
    1150             : }
    1151             : 
    1152      133080 : AP_RunCam *AP::runcam() {
    1153      133080 :     return AP_RunCam::get_singleton();
    1154             : }
    1155             : 
    1156             : #endif  // AP_CAMERA_RUNCAM_ENABLED

Generated by: LCOV version 1.14