/* Siyi gimbal driver using custom serial protocol Packet format (courtesy of Siyi's SDK document) ------------------------------------------------------------------------------------------- Field Index Bytes Description ------------------------------------------------------------------------------------------- STX 0 2 0x5566: starting mark CTRL 2 1 bit 0: need_ack. set if the current data packet needs ack bit 1: ack_pack. set if the current data packate IS an ack bit 2-7: reserved Data_len 3 2 Data field byte length. Low byte in the front SEQ 5 2 Frame sequence (0 ~ 65535). Low byte in the front. May be used to detect packet loss CMD_ID 7 1 Command ID DATA 8 Data_len Data CRC16 2 CRC16 check the complete data package. Low byte in the front */ #pragma once #include "AP_Mount_config.h" #if HAL_MOUNT_SIYI_ENABLED #include "AP_Mount_Backend_Serial.h" #include #include #include #define AP_MOUNT_SIYI_PACKETLEN_MAX 42 // maximum number of bytes in a packet sent to or received from the gimbal class AP_Mount_Siyi : public AP_Mount_Backend_Serial { public: // Constructor using AP_Mount_Backend_Serial::AP_Mount_Backend_Serial; /* Do not allow copies */ CLASS_NO_COPY(AP_Mount_Siyi); // update mount position - should be called periodically void update() override; // return true if healthy bool healthy() const override; // return true if this mount accepts roll targets bool has_roll_control() const override { return false; } // has_pan_control - returns true if this mount can control its pan (required for multicopters) bool has_pan_control() const override { return yaw_range_valid(); }; // // camera controls // // take a picture. returns true on success bool take_picture() override; // start or stop video recording // set start_recording = true to start record, false to stop recording bool record_video(bool start_recording) override; // set zoom specified as a rate or percentage bool set_zoom(ZoomType zoom_type, float zoom_value) override; // set focus specified as rate, percentage or auto // focus in = -1, focus hold = 0, focus out = 1 SetFocusResult set_focus(FocusType focus_type, float focus_value) override; // set camera lens as a value from 0 to 8. ZT30 only bool set_lens(uint8_t lens) override; // set_camera_source is functionally the same as set_lens except primary and secondary lenses are specified by type // primary and secondary sources use the AP_Camera::CameraSource enum cast to uint8_t bool set_camera_source(uint8_t primary_source, uint8_t secondary_source) override; // send camera information message to GCS void send_camera_information(mavlink_channel_t chan) const override; // send camera settings message to GCS void send_camera_settings(mavlink_channel_t chan) const override; #if AP_MOUNT_SEND_THERMAL_RANGE_ENABLED // send camera thermal range message to GCS void send_camera_thermal_range(mavlink_channel_t chan) const override; #endif // change camera settings not normally used by autopilot // THERMAL_PALETTE: 0:WhiteHot, 2:Sepia, 3:IronBow, 4:Rainbow, 5:Night, 6:Aurora, 7:RedHot, 8:Jungle, 9:Medical, 10:BlackHot, 11:GloryHot // THERMAL_GAIN: 0:Low gain (50C ~ 550C), 1:High gain (-20C ~ 150C) // THERMAL_RAW_DATA: 0:Disable Raw Data (30fps), 1:Enable Raw Data (25fps) bool change_setting(CameraSetting setting, float value) override; // // rangefinder // // get rangefinder distance. Returns true on success bool get_rangefinder_distance(float& distance_m) const override; protected: // get attitude as a quaternion. returns true on success bool get_attitude_quaternion(Quaternion& att_quat) override; // get angular velocity of mount. Only available on some backends bool get_angular_velocity(Vector3f& rates) override { rates = _current_rates_rads; return true; } private: // serial protocol command ids enum class SiyiCommandId { ACQUIRE_FIRMWARE_VERSION = 0x01, HARDWARE_ID = 0x02, AUTO_FOCUS = 0x04, MANUAL_ZOOM_AND_AUTO_FOCUS = 0x05, MANUAL_FOCUS = 0x06, GIMBAL_ROTATION = 0x07, CENTER = 0x08, ACQUIRE_GIMBAL_CONFIG_INFO = 0x0A, FUNCTION_FEEDBACK_INFO = 0x0B, PHOTO = 0x0C, ACQUIRE_GIMBAL_ATTITUDE = 0x0D, ABSOLUTE_ZOOM = 0x0F, SET_CAMERA_IMAGE_TYPE = 0x11, GET_TEMP_FULL_IMAGE = 0x14, READ_RANGEFINDER = 0x15, SET_THERMAL_PALETTE = 0x1B, EXTERNAL_ATTITUDE = 0x22, SET_TIME = 0x30, SET_THERMAL_RAW_DATA = 0x34, SET_THERMAL_GAIN = 0x38, POSITION_DATA = 0x3e, }; // Function Feedback Info packet info_type values enum class FunctionFeedbackInfo : uint8_t { SUCCESS = 0, FAILED_TO_TAKE_PHOTO = 1, HDR_ON = 2, HDR_OFF = 3, FAILED_TO_RECORD_VIDEO = 4 }; // Photo Function packet func_type values enum class PhotoFunction : uint8_t { TAKE_PICTURE = 0, HDR_TOGGLE = 1, RECORD_VIDEO_TOGGLE = 2, LOCK_MODE = 3, FOLLOW_MODE = 4, FPV_MODE = 5 }; // parsing state enum class ParseState : uint8_t { WAITING_FOR_HEADER_LOW, WAITING_FOR_HEADER_HIGH, WAITING_FOR_CTRL, WAITING_FOR_DATALEN_LOW, WAITING_FOR_DATALEN_HIGH, WAITING_FOR_SEQ_LOW, WAITING_FOR_SEQ_HIGH, WAITING_FOR_CMDID, WAITING_FOR_DATA, WAITING_FOR_CRC_LOW, WAITING_FOR_CRC_HIGH, }; // hardware model enum enum class HardwareModel : uint8_t { UNKNOWN = 0, A2, A8, ZR10, ZR30, ZT6, ZT30 } _hardware_model; enum class HdrStatus : uint8_t { OFF = 0, ON = 1, }; enum class RecordingStatus : uint8_t { OFF = 0, ON = 1, NO_CARD = 2, DATA_LOSS = 3, }; enum class GimbalMotionMode : uint8_t { LOCK = 0, FOLLOW = 1, FPV = 2, }; enum class GimbalMountingDirection : uint8_t { UNDEFINED = 0, NORMAL = 1, UPSIDE_DOWN = 2, }; enum class VideoOutputStatus : uint8_t { HDMI = 0, CVBS = 1, }; // Response message for "Acquire Gimbal Confuguration Information" (0x0A) typedef struct { uint8_t _reserved1; HdrStatus hdr_status; uint8_t _reserved3; RecordingStatus record_status; GimbalMotionMode motion_mode; GimbalMountingDirection mounting_dir; VideoOutputStatus video_mode; } GimbalConfigInfo; static_assert(sizeof(GimbalConfigInfo) == 7, "GimbalConfigInfo must be 7 bytes"); // camera image types (aka lens) enum class CameraImageType : uint8_t { MAIN_PIP_ZOOM_THERMAL_SUB_WIDEANGLE = 0, MAIN_PIP_WIDEANGLE_THERMAL_SUB_ZOOM = 1, MAIN_PIP_ZOOM_WIDEANGLE_SUB_THERMAL = 2, MAIN_ZOOM_SUB_THERMAL = 3, MAIN_ZOOM_SUB_WIDEANGLE = 4, MAIN_WIDEANGLE_SUB_THERMAL = 5, MAIN_WIDEANGLE_SUB_ZOOM = 6, MAIN_THERMAL_SUB_ZOOM = 7, MAIN_THERMAL_SUB_WIDEANGLE = 8 }; typedef struct { uint8_t major; uint8_t minor; uint8_t patch; } Version; typedef struct { Version camera; Version gimbal; Version zoom; bool received; // true once version information has been received } FirmwareVersion; // reading incoming packets from gimbal and confirm they are of the correct format // results are held in the _parsed_msg structure void read_incoming_packets(); // process successfully decoded packets held in the _parsed_msg structure void process_packet(); // send packet to gimbal // returns true on success, false if outgoing serial buffer is full bool send_packet(SiyiCommandId cmd_id, const uint8_t* databuff, uint8_t databuff_len); bool send_1byte_packet(SiyiCommandId cmd_id, uint8_t data_byte); // request info from gimbal void request_firmware_version() { send_packet(SiyiCommandId::ACQUIRE_FIRMWARE_VERSION, nullptr, 0); } void request_hardware_id() { send_packet(SiyiCommandId::HARDWARE_ID, nullptr, 0); } void request_configuration() { send_packet(SiyiCommandId::ACQUIRE_GIMBAL_CONFIG_INFO, nullptr, 0); } void request_function_feedback_info() { send_packet(SiyiCommandId::FUNCTION_FEEDBACK_INFO, nullptr, 0); } void request_gimbal_attitude() { send_packet(SiyiCommandId::ACQUIRE_GIMBAL_ATTITUDE, nullptr, 0); } void request_rangefinder_distance() { send_packet(SiyiCommandId::READ_RANGEFINDER, nullptr, 0); } // rotate gimbal. pitch_rate and yaw_rate are scalars in the range -100 ~ +100 // yaw_is_ef should be true if gimbal should maintain an earth-frame target (aka lock) void rotate_gimbal(int8_t pitch_scalar, int8_t yaw_scalar, bool yaw_is_ef); // Set gimbal's motion mode if it has changed. Use force=true to always send. // FOLLOW: roll and pitch are in earth-frame, yaw is in body-frame // LOCK: roll, pitch and yaw are all in earth-frame // FPV: roll, pitch and yaw are all in body-frame // Returns true if message successfully sent to Gimbal bool set_motion_mode(const GimbalMotionMode mode, const bool force=false); // send target pitch and yaw rates to gimbal // yaw_is_ef should be true if yaw_rads target is an earth frame rate, false if body_frame void send_target_rates(float pitch_rads, float yaw_rads, bool yaw_is_ef); // send target pitch and yaw angles to gimbal // yaw_is_ef should be true if yaw_rad target is an earth frame angle, false if body_frame void send_target_angles(float pitch_rad, float yaw_rad, bool yaw_is_ef); // send zoom rate command to camera. zoom out = -1, hold = 0, zoom in = 1 bool send_zoom_rate(float zoom_value); // send zoom multiple command to camera. e.g. 1x, 10x, 30x bool send_zoom_mult(float zoom_mult); // get zoom multiple max float get_zoom_mult_max() const; // update zoom controller void update_zoom_control(); // get model name string, returns nullptr if hardware id is unknown const char* get_model_name() const; // Checks that the firmware version on the Gimbal meets the minimum supported version. void check_firmware_version() const; #if AP_MOUNT_SEND_THERMAL_RANGE_ENABLED // get thermal min/max if available at 5hz void request_thermal_minmax(); #endif // internal variables bool _got_hardware_id; // true once hardware id ha been received FirmwareVersion _fw_version; // firmware version (for reporting for GCS) // buffer holding bytes from latest packet. This is only used to calculate the crc uint8_t _msg_buff[AP_MOUNT_SIYI_PACKETLEN_MAX]; uint8_t _msg_buff_len; const uint8_t _msg_buff_data_start = 8; // data starts at this byte of _msg_buff // parser state and unpacked fields struct PACKED { uint16_t data_len; // expected number of data bytes uint8_t command_id; // command id uint16_t data_bytes_received; // number of data bytes received so far uint16_t crc16; // latest message's crc ParseState state; // state of incoming message processing } _parsed_msg; // variables for sending packets to gimbal uint32_t _last_send_ms; // system time (in milliseconds) of last packet sent to gimbal uint16_t _last_seq; // last sequence number used (should be increment for each send) // actual attitude received from gimbal Vector3f _current_angle_rad; // current angles in radians received from gimbal (x=roll, y=pitch, z=yaw) Vector3f _current_rates_rads; // current angular rates in rad/s (x=roll, y=pitch, z=yaw) uint32_t _last_current_angle_rad_ms; // system time _current_angle_rad was updated uint32_t _last_req_current_angle_rad_ms; // system time that this driver last requested current angle // absolute zoom control. only used for A8 that does not support abs zoom control ZoomType _zoom_type; // current zoom type float _zoom_rate_target; // current zoom rate target float _zoom_mult; // most recent actual zoom multiple received from camera uint32_t _last_zoom_control_ms; // system time that zoom control was last run // Configuration info received from gimbal GimbalConfigInfo _config_info; // rangefinder variables uint32_t _last_rangefinder_req_ms; // system time of last request for rangefinder distance uint32_t _last_rangefinder_dist_ms; // system time of last successful read of rangefinder distance float _rangefinder_dist_m; // distance received from rangefinder // sending of attitude and position to gimbal uint32_t _last_attitude_send_ms; void send_attitude_position(void); // hardware lookup table indexed by HardwareModel enum values (see above) struct HWInfo { uint8_t hwid[2]; const char* model_name; }; static const HWInfo hardware_lookup_table[]; #if AP_MOUNT_SEND_THERMAL_RANGE_ENABLED // thermal variables struct { uint32_t last_req_ms; // system time of last request for thermal min/max temperatures uint32_t last_update_ms; // system time of last update of thermal min/max temperatures float max_C; // thermal max temp in C float min_C; // thermal min temp in C Vector2ui max_pos; // thermal max temp position on image in pixels. x=0 is left, y=0 is top Vector2ui min_pos; // thermal min temp position on image in pixels. x=0 is left, y=0 is top } _thermal; #endif // count of SET_TIME packets, we send 5 times to cope with packet loss uint8_t sent_time_count; }; #endif // HAL_MOUNT_SIYI_ENABLED