LCOV - code coverage report
Current view: top level - AP_Mount - AP_Mount_Siyi.h (source / functions) Hit Total Coverage
Test: lcov.info Lines: 17 19 89.5 %
Date: 2026-09-09 11:02:08 Functions: 12 14 85.7 %

          Line data    Source code
       1             : /*
       2             :   Siyi gimbal driver using custom serial protocol
       3             : 
       4             :   Packet format (courtesy of Siyi's SDK document)
       5             : 
       6             :   -------------------------------------------------------------------------------------------
       7             :   Field     Index   Bytes       Description
       8             :   -------------------------------------------------------------------------------------------
       9             :   STX       0       2           0x5566: starting mark
      10             :   CTRL      2       1           bit 0: need_ack.  set if the current data packet needs ack
      11             :                                 bit 1: ack_pack.  set if the current data packate IS an ack
      12             :                                 bit 2-7: reserved
      13             :   Data_len  3       2           Data field byte length.  Low byte in the front
      14             :   SEQ       5       2           Frame sequence (0 ~ 65535).  Low byte in the front.  May be used to detect packet loss
      15             :   CMD_ID    7       1           Command ID
      16             :   DATA      8       Data_len    Data
      17             :   CRC16             2           CRC16 check the complete data package.  Low byte in the front
      18             :  */
      19             : 
      20             : #pragma once
      21             : 
      22             : #include "AP_Mount_config.h"
      23             : 
      24             : #if HAL_MOUNT_SIYI_ENABLED
      25             : 
      26             : #include "AP_Mount_Backend_Serial.h"
      27             : 
      28             : #include <AP_HAL/AP_HAL.h>
      29             : #include <AP_Math/AP_Math.h>
      30             : #include <AP_Common/AP_Common.h>
      31             : 
      32             : #define AP_MOUNT_SIYI_PACKETLEN_MAX     42  // maximum number of bytes in a packet sent to or received from the gimbal
      33             : 
      34             : class AP_Mount_Siyi : public AP_Mount_Backend_Serial
      35             : {
      36             : 
      37             : public:
      38             :     // Constructor
      39             :     using AP_Mount_Backend_Serial::AP_Mount_Backend_Serial;
      40             : 
      41             :     /* Do not allow copies */
      42             :     CLASS_NO_COPY(AP_Mount_Siyi);
      43             : 
      44             :     // update mount position - should be called periodically
      45             :     void update() override;
      46             : 
      47             :     // return true if healthy
      48             :     bool healthy() const override;
      49             : 
      50             :     // return true if this mount accepts roll targets
      51           0 :     bool has_roll_control() const override { return false; }
      52             : 
      53             :     // has_pan_control - returns true if this mount can control its pan (required for multicopters)
      54           0 :     bool has_pan_control() const override { return yaw_range_valid(); };
      55             : 
      56             :     //
      57             :     // camera controls
      58             :     //
      59             : 
      60             :     // take a picture.  returns true on success
      61             :     bool take_picture() override;
      62             : 
      63             :     // start or stop video recording
      64             :     // set start_recording = true to start record, false to stop recording
      65             :     bool record_video(bool start_recording) override;
      66             : 
      67             :     // set zoom specified as a rate or percentage
      68             :     bool set_zoom(ZoomType zoom_type, float zoom_value) override;
      69             : 
      70             :     // set focus specified as rate, percentage or auto
      71             :     // focus in = -1, focus hold = 0, focus out = 1
      72             :     SetFocusResult set_focus(FocusType focus_type, float focus_value) override;
      73             : 
      74             :     // set camera lens as a value from 0 to 8.  ZT30 only
      75             :     bool set_lens(uint8_t lens) override;
      76             : 
      77             :     // set_camera_source is functionally the same as set_lens except primary and secondary lenses are specified by type
      78             :     // primary and secondary sources use the AP_Camera::CameraSource enum cast to uint8_t
      79             :     bool set_camera_source(uint8_t primary_source, uint8_t secondary_source) override;
      80             : 
      81           2 :     bool has_camera_information() const override { return true; }
      82             :     // return camera vendor name
      83           2 :     void get_camera_vendor_name(char *buf, uint8_t buflen) const override { strncpy(buf, "Siyi", buflen); }
      84             :     // return camera model name
      85           2 :     void get_camera_model_name(char *buf, uint8_t buflen) const override { strncpy(buf, get_model_name(), buflen); }
      86             :     // return camera firmware version
      87           2 :     uint32_t get_camera_firmware_version() const override {
      88           2 :         return _fw_version.camera.major | (_fw_version.camera.minor << 8) | (_fw_version.camera.patch << 16);
      89             :     }
      90             :     // return focal length in mm for the current hardware model
      91             :     float get_camera_focal_length_mm() const override;
      92             :     // return camera capability flags
      93           2 :     uint32_t get_camera_cap_flags() const override {
      94             :         return (CAMERA_CAP_FLAGS_CAPTURE_VIDEO |
      95             :                 CAMERA_CAP_FLAGS_CAPTURE_IMAGE |
      96             :                 CAMERA_CAP_FLAGS_HAS_BASIC_ZOOM |
      97           2 :                 CAMERA_CAP_FLAGS_HAS_BASIC_FOCUS);
      98             :     }
      99             : 
     100             :     // send camera settings message to GCS
     101             :     void send_camera_settings(mavlink_channel_t chan) const override;
     102             : 
     103             : #if AP_MOUNT_SEND_THERMAL_RANGE_ENABLED
     104             :     // send camera thermal range message to GCS
     105             :     void send_camera_thermal_range(mavlink_channel_t chan) const override;
     106             : #endif
     107             : 
     108             :     // change camera settings not normally used by autopilot
     109             :     // THERMAL_PALETTE: 0:WhiteHot, 2:Sepia, 3:IronBow, 4:Rainbow, 5:Night, 6:Aurora, 7:RedHot, 8:Jungle, 9:Medical, 10:BlackHot, 11:GloryHot
     110             :     // THERMAL_GAIN: 0:Low gain (50C ~ 550C), 1:High gain (-20C ~ 150C)
     111             :     // THERMAL_RAW_DATA: 0:Disable Raw Data (30fps), 1:Enable Raw Data (25fps)
     112             :     bool change_setting(CameraSetting setting, float value) override;
     113             : 
     114             :     //
     115             :     // rangefinder
     116             :     //
     117             : 
     118             :     // get rangefinder distance.  Returns true on success
     119             :     bool get_rangefinder_distance(float& distance_m) const override;
     120             : 
     121             : protected:
     122             : 
     123             :     // get attitude as a quaternion.  returns true on success
     124             :     bool get_attitude_quaternion(Quaternion& att_quat) override;
     125             : 
     126             :     // get angular velocity of mount. Only available on some backends
     127          13 :     bool get_angular_velocity(Vector3f& rates) override {
     128          13 :         rates = _current_rates_rads;
     129          13 :         return true;
     130             :     }
     131             :     
     132             : private:
     133             : 
     134             :     // serial protocol command ids
     135             :     enum class SiyiCommandId {
     136             :         ACQUIRE_FIRMWARE_VERSION = 0x01,
     137             :         HARDWARE_ID = 0x02,
     138             :         AUTO_FOCUS = 0x04,
     139             :         MANUAL_ZOOM_AND_AUTO_FOCUS = 0x05,
     140             :         MANUAL_FOCUS = 0x06,
     141             :         GIMBAL_ROTATION = 0x07,
     142             :         CENTER = 0x08,
     143             :         ACQUIRE_GIMBAL_CONFIG_INFO = 0x0A,
     144             :         FUNCTION_FEEDBACK_INFO = 0x0B,
     145             :         PHOTO = 0x0C,
     146             :         ACQUIRE_GIMBAL_ATTITUDE = 0x0D,
     147             :         ABSOLUTE_ZOOM = 0x0F,
     148             :         SET_CAMERA_IMAGE_TYPE = 0x11,
     149             :         GET_TEMP_FULL_IMAGE = 0x14,
     150             :         READ_RANGEFINDER = 0x15,
     151             :         SET_THERMAL_PALETTE = 0x1B,
     152             :         EXTERNAL_ATTITUDE = 0x22,
     153             :         SET_TIME = 0x30,
     154             :         SET_THERMAL_RAW_DATA = 0x34,
     155             :         SET_THERMAL_GAIN = 0x38,
     156             :         POSITION_DATA = 0x3e,
     157             :     };
     158             : 
     159             :     // Function Feedback Info packet info_type values
     160             :     enum class FunctionFeedbackInfo : uint8_t {
     161             :         SUCCESS = 0,
     162             :         FAILED_TO_TAKE_PHOTO = 1,
     163             :         HDR_ON = 2,
     164             :         HDR_OFF = 3,
     165             :         FAILED_TO_RECORD_VIDEO = 4
     166             :     };
     167             : 
     168             :     // Photo Function packet func_type values
     169             :     enum class PhotoFunction : uint8_t {
     170             :         TAKE_PICTURE = 0,
     171             :         HDR_TOGGLE = 1,
     172             :         RECORD_VIDEO_TOGGLE = 2,
     173             :         LOCK_MODE = 3,
     174             :         FOLLOW_MODE = 4,
     175             :         FPV_MODE = 5
     176             :     };
     177             : 
     178             :     // parsing state
     179             :     enum class ParseState : uint8_t {
     180             :         WAITING_FOR_HEADER_LOW,
     181             :         WAITING_FOR_HEADER_HIGH,
     182             :         WAITING_FOR_CTRL,
     183             :         WAITING_FOR_DATALEN_LOW,
     184             :         WAITING_FOR_DATALEN_HIGH,
     185             :         WAITING_FOR_SEQ_LOW,
     186             :         WAITING_FOR_SEQ_HIGH,
     187             :         WAITING_FOR_CMDID,
     188             :         WAITING_FOR_DATA,
     189             :         WAITING_FOR_CRC_LOW,
     190             :         WAITING_FOR_CRC_HIGH,
     191             :     };
     192             : 
     193             :     // hardware model enum
     194             :     enum class HardwareModel : uint8_t {
     195             :         UNKNOWN = 0,
     196             :         A2,
     197             :         A8,
     198             :         ZR10,
     199             :         ZR30,
     200             :         ZT6,
     201             :         ZT30
     202             :     } _hardware_model;
     203             : 
     204             :     enum class HdrStatus : uint8_t {
     205             :         OFF = 0,
     206             :         ON  = 1,
     207             :     };
     208             : 
     209             :     enum class RecordingStatus : uint8_t {
     210             :         OFF       = 0,
     211             :         ON        = 1,
     212             :         NO_CARD   = 2,
     213             :         DATA_LOSS = 3,
     214             :     };
     215             : 
     216             :     enum class GimbalMotionMode : uint8_t {
     217             :         LOCK   = 0,
     218             :         FOLLOW = 1,
     219             :         FPV    = 2,
     220             :     };
     221             : 
     222             :     enum class GimbalMountingDirection : uint8_t {
     223             :         UNDEFINED = 0,
     224             :         NORMAL = 1,
     225             :         UPSIDE_DOWN = 2,
     226             :     };
     227             : 
     228             :     enum class VideoOutputStatus : uint8_t {
     229             :         HDMI = 0,
     230             :         CVBS = 1,
     231             :     };
     232             : 
     233             :     // Response message for "Acquire Gimbal Confuguration Information" (0x0A)
     234             :     typedef struct {
     235             :         uint8_t _reserved1;
     236             :         HdrStatus hdr_status;
     237             :         uint8_t _reserved3;
     238             :         RecordingStatus record_status;
     239             :         GimbalMotionMode motion_mode;
     240             :         GimbalMountingDirection mounting_dir;
     241             :         VideoOutputStatus video_mode;
     242             :     } GimbalConfigInfo;
     243             :     static_assert(sizeof(GimbalConfigInfo) == 7, "GimbalConfigInfo must be 7 bytes");
     244             : 
     245             :     // camera image types (aka lens)
     246             :     enum class CameraImageType : uint8_t {
     247             :         MAIN_PIP_ZOOM_THERMAL_SUB_WIDEANGLE = 0,
     248             :         MAIN_PIP_WIDEANGLE_THERMAL_SUB_ZOOM = 1,
     249             :         MAIN_PIP_ZOOM_WIDEANGLE_SUB_THERMAL = 2,
     250             :         MAIN_ZOOM_SUB_THERMAL = 3,
     251             :         MAIN_ZOOM_SUB_WIDEANGLE = 4,
     252             :         MAIN_WIDEANGLE_SUB_THERMAL = 5,
     253             :         MAIN_WIDEANGLE_SUB_ZOOM = 6,
     254             :         MAIN_THERMAL_SUB_ZOOM = 7,
     255             :         MAIN_THERMAL_SUB_WIDEANGLE = 8
     256             :     };
     257             : 
     258             :     typedef struct {
     259             :         uint8_t major;
     260             :         uint8_t minor;
     261             :         uint8_t patch;
     262             :     } Version;
     263             :     typedef struct {
     264             :         Version camera;
     265             :         Version gimbal;
     266             :         Version zoom;
     267             :         bool received; // true once version information has been received
     268             :     } FirmwareVersion;
     269             : 
     270             :     // reading incoming packets from gimbal and confirm they are of the correct format
     271             :     // results are held in the _parsed_msg structure
     272             :     void read_incoming_packets();
     273             : 
     274             :     // process successfully decoded packets held in the _parsed_msg structure
     275             :     void process_packet();
     276             : 
     277             :     // send packet to gimbal
     278             :     // returns true on success, false if outgoing serial buffer is full
     279             :     bool send_packet(SiyiCommandId cmd_id, const uint8_t* databuff, uint8_t databuff_len);
     280             :     bool send_1byte_packet(SiyiCommandId cmd_id, uint8_t data_byte);
     281             : 
     282             :     // request info from gimbal
     283           1 :     void request_firmware_version() { send_packet(SiyiCommandId::ACQUIRE_FIRMWARE_VERSION, nullptr, 0); }
     284           1 :     void request_hardware_id() { send_packet(SiyiCommandId::HARDWARE_ID, nullptr, 0); }
     285           2 :     void request_configuration() { send_packet(SiyiCommandId::ACQUIRE_GIMBAL_CONFIG_INFO, nullptr, 0); }
     286             :     void request_function_feedback_info() { send_packet(SiyiCommandId::FUNCTION_FEEDBACK_INFO, nullptr, 0); }
     287          43 :     void request_gimbal_attitude() { send_packet(SiyiCommandId::ACQUIRE_GIMBAL_ATTITUDE, nullptr, 0); }
     288          22 :     void request_rangefinder_distance() { send_packet(SiyiCommandId::READ_RANGEFINDER, nullptr, 0); }
     289             : 
     290             :     // rotate gimbal.  pitch_rate and yaw_rate are scalars in the range -100 ~ +100
     291             :     // yaw_is_ef should be true if gimbal should maintain an earth-frame target (aka lock)
     292             :     void rotate_gimbal(int8_t pitch_scalar, int8_t yaw_scalar, bool yaw_is_ef);
     293             : 
     294             :     // Set gimbal's motion mode if it has changed. Use force=true to always send.
     295             :     //   FOLLOW: roll and pitch are in earth-frame, yaw is in body-frame
     296             :     //   LOCK: roll, pitch and yaw are all in earth-frame
     297             :     //   FPV: roll, pitch and yaw are all in body-frame
     298             :     // Returns true if message successfully sent to Gimbal
     299             :     bool set_motion_mode(const GimbalMotionMode mode, const bool force=false);
     300             : 
     301             :     // Siyi can send either rates or angles
     302         127 :     uint8_t natively_supported_mount_target_types() const override {
     303         127 :         return NATIVE_ANGLES_AND_RATES_ONLY;
     304             :     };
     305             : 
     306             :     // send target pitch and yaw rates to gimbal
     307             :     void send_target_rates(const MountRateTarget &rate_rads) override;
     308             : 
     309             :     // send target pitch and yaw angles to gimbal
     310             :     void send_target_angles(const MountAngleTarget &angle_rad) override;
     311             : 
     312             :     // send zoom rate command to camera. zoom out = -1, hold = 0, zoom in = 1
     313             :     bool send_zoom_rate(float zoom_value);
     314             : 
     315             :     // send zoom multiple command to camera. e.g. 1x, 10x, 30x
     316             :     bool send_zoom_mult(float zoom_mult);
     317             : 
     318             :     // get zoom multiple max
     319             :     float get_zoom_mult_max() const;
     320             : 
     321             :     // update zoom controller
     322             :     void update_zoom_control();
     323             : 
     324             :     // get model name string, returns nullptr if hardware id is unknown
     325             :     const char* get_model_name() const;
     326             : 
     327             :     // Checks that the firmware version on the Gimbal meets the minimum supported version.
     328             :     void check_firmware_version() const;
     329             : 
     330             : #if AP_MOUNT_SEND_THERMAL_RANGE_ENABLED
     331             :     // get thermal min/max if available at 5hz
     332             :     void request_thermal_minmax();
     333             : #endif
     334             : 
     335             :     // internal variables
     336             :     bool _got_hardware_id;                          // true once hardware id ha been received
     337             : 
     338             :     FirmwareVersion _fw_version;                    // firmware version (for reporting for GCS)
     339             : 
     340             :     // buffer holding bytes from latest packet.  This is only used to calculate the crc
     341             :     uint8_t _msg_buff[AP_MOUNT_SIYI_PACKETLEN_MAX];
     342             :     uint8_t _msg_buff_len;
     343             :     static constexpr uint8_t _msg_buff_data_start = 8;         // data starts at this byte of _msg_buff
     344             : 
     345             :     // parser state and unpacked fields
     346             :     struct PACKED {
     347             :         uint16_t data_len;                          // expected number of data bytes
     348             :         uint8_t command_id;                         // command id
     349             :         uint16_t data_bytes_received;               // number of data bytes received so far
     350             :         uint16_t crc16;                             // latest message's crc
     351             :         ParseState state;                           // state of incoming message processing
     352             :     } _parsed_msg;
     353             : 
     354             :     // variables for sending packets to gimbal
     355             :     uint32_t _last_send_ms;                         // system time (in milliseconds) of last packet sent to gimbal
     356             :     uint16_t _last_seq;                             // last sequence number used (should be increment for each send)
     357             : 
     358             :     // actual attitude received from gimbal
     359             :     Vector3f _current_angle_rad;                    // current angles in radians received from gimbal (x=roll, y=pitch, z=yaw)
     360             :     Vector3f _current_rates_rads;                   // current angular rates in rad/s (x=roll, y=pitch, z=yaw)
     361             :     uint32_t _last_current_angle_rad_ms;            // system time _current_angle_rad was updated
     362             :     uint32_t _last_req_current_angle_rad_ms;        // system time that this driver last requested current angle
     363             : 
     364             :     // absolute zoom control.  only used for A8 that does not support abs zoom control
     365             :     ZoomType _zoom_type;                            // current zoom type
     366             :     float _zoom_rate_target;                        // current zoom rate target
     367             :     float _zoom_mult;                               // most recent actual zoom multiple received from camera
     368             :     uint32_t _last_zoom_control_ms;                 // system time that zoom control was last run
     369             : 
     370             :     // Configuration info received from gimbal
     371             :     GimbalConfigInfo _config_info;
     372             :     
     373             :     // rangefinder variables
     374             :     uint32_t _last_rangefinder_req_ms;              // system time of last request for rangefinder distance
     375             :     uint32_t _last_rangefinder_dist_ms;             // system time of last successful read of rangefinder distance
     376             :     float _rangefinder_dist_m;                      // distance received from rangefinder
     377             : 
     378             :     // sending of attitude and position to gimbal
     379             :     uint32_t _last_attitude_send_ms;
     380             :     void send_attitude_position(void);
     381             : 
     382             :     // hardware lookup table indexed by HardwareModel enum values (see above)
     383             :     struct HWInfo {
     384             :         uint8_t hwid[2];
     385             :         const char* model_name;
     386             :     };
     387             :     static const HWInfo hardware_lookup_table[];
     388             : 
     389             : #if AP_MOUNT_SEND_THERMAL_RANGE_ENABLED
     390             :     // thermal variables
     391             :     struct {
     392             :         uint32_t last_req_ms;       // system time of last request for thermal min/max temperatures
     393             :         uint32_t last_update_ms;    // system time of last update of thermal min/max temperatures
     394             :         float max_C;                // thermal max temp in C
     395             :         float min_C;                // thermal min temp in C
     396             :         Vector2ui max_pos;          // thermal max temp position on image in pixels. x=0 is left, y=0 is top
     397             :         Vector2ui min_pos;          // thermal min temp position on image in pixels. x=0 is left, y=0 is top
     398             :     } _thermal;
     399             : #endif
     400             : 
     401             :     // count of SET_TIME packets, we send 5 times to cope with packet loss
     402             :     uint8_t sent_time_count;
     403             : };
     404             : 
     405             : #endif // HAL_MOUNT_SIYI_ENABLED

Generated by: LCOV version 1.14