LCOV - code coverage report
Current view: top level - AP_Mount - AP_Mount_Viewpro.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 202 409 49.4 %
Date: 2026-09-09 11:02:08 Functions: 14 28 50.0 %

          Line data    Source code
       1             : #include "AP_Mount_config.h"
       2             : 
       3             : #if HAL_MOUNT_VIEWPRO_ENABLED
       4             : 
       5             : #include "AP_Mount_Viewpro.h"
       6             : 
       7             : #include <AP_HAL/AP_HAL.h>
       8             : #include <AP_AHRS/AP_AHRS.h>
       9             : #include <AP_GPS/AP_GPS.h>
      10             : #include <AP_RTC/AP_RTC.h>
      11             : #include <GCS_MAVLink/GCS.h>
      12             : #include <GCS_MAVLink/include/mavlink/v2.0/checksum.h>
      13             : 
      14             : extern const AP_HAL::HAL& hal;
      15             : 
      16             : #define AP_MOUNT_VIEWPRO_HEADER1       0x55     // first header byte
      17             : #define AP_MOUNT_VIEWPRO_HEADER2       0xAA     // second header byte
      18             : #define AP_MOUNT_VIEWPRO_HEADER3       0xDC     // third header byte
      19             : #define AP_MOUNT_VIEWPRO_PACKETLEN_MIN 5        // min number of bytes in a packet (3 header bytes, length, crc)
      20             : #define AP_MOUNT_VIEWPRO_DATALEN_MAX   (AP_MOUNT_VIEWPRO_PACKETLEN_MAX-AP_MOUNT_VIEWPRO_PACKETLEN_MIN) // max bytes for data portion of packet
      21             : #define AP_MOUNT_VIEWPRO_HEALTH_TIMEOUT_MS 1000 // state will become unhealthy if no attitude is received within this timeout
      22             : #define AP_MOUNT_VIEWPRO_UPDATE_INTERVAL_MS 100 // resend angle or rate targets to gimbal at this interval
      23             : #define AP_MOUNT_VIEWPRO_EO_ZOOM_SPEED     0x07    // hard-coded zoom speed (fast)
      24             : #define AP_MOUNT_VIEWPRO_IR_ZOOM_SPEED     1 
      25             : #define AP_MOUNT_VIEWPRO_ZOOM_MAX       10      // hard-coded absolute zoom times max
      26             : #define AP_MOUNT_VIEWPRO_RAD_TO_OUTPUT  (65536.0 / M_2PI)   // scalar to convert radians to the viewpro angle scaling
      27             : #define AP_MOUNT_VIEWPRO_OUTPUT_TO_DEG  (360.0 / 65536.0)   // scalar to convert viewpro angle scaling to degrees
      28             : 
      29             : #define AP_MOUNT_VIEWPRO_DEBUG 0
      30             : #define debug(fmt, args ...) do { if (AP_MOUNT_VIEWPRO_DEBUG) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Viewpro: " fmt, ## args); } } while (0)
      31             : 
      32             : const char* AP_Mount_Viewpro::send_text_prefix = "Viewpro:";
      33             : 
      34             : // update mount position - should be called periodically
      35       10279 : void AP_Mount_Viewpro::update()
      36             : {
      37       10279 :     AP_Mount_Backend::update();
      38             : 
      39             :     // exit immediately if not initialised
      40       10279 :     if (!_initialised) {
      41           0 :         return;
      42             :     }
      43             : 
      44             :     // below here we sent angle or rate targets
      45             :     // throttle sends of target angles or rates
      46       10279 :     uint32_t now_ms = AP_HAL::millis();
      47       10279 :     if (now_ms - _last_update_ms < AP_MOUNT_VIEWPRO_UPDATE_INTERVAL_MS) {
      48        8223 :         return;
      49             :     }
      50        2056 :     _last_update_ms = now_ms;
      51             : 
      52             :     // reading incoming packets from gimbal
      53        2056 :     read_incoming_packets();
      54             : 
      55             :     // request model name
      56        2056 :     if (!_got_model_name) {
      57           1 :         send_comm_config_cmd(CommConfigCmd::QUERY_MODEL);
      58             :     }
      59             : 
      60             :     // request firmware version
      61        2056 :     if (!_got_firmware_version) {
      62           1 :         send_comm_config_cmd(CommConfigCmd::QUERY_FIRMWARE_VER);
      63             :     }
      64             : 
      65             :     // send handshake
      66        2056 :     send_handshake();
      67             : 
      68             :     // send vehicle attitude and position
      69        2056 :     send_m_ahrs();
      70             : 
      71             :     // if tracking is active we do not send new targets to the gimbal
      72        2056 :     if (_last_tracking_status == TrackingStatus::SEARCHING || _last_tracking_status == TrackingStatus::TRACKING) {
      73           0 :         return;
      74             :     }
      75             : 
      76             :     // update based on mount mode
      77        2056 :     update_mnt_target();
      78             : 
      79             :     // send target angles or rates depending on the target type
      80        2056 :     send_target_to_gimbal();
      81             : }
      82             : 
      83             : // return true if healthy
      84        2105 : bool AP_Mount_Viewpro::healthy() const
      85             : {
      86             :     // unhealthy until gimbal has been found and replied with firmware version info
      87        2105 :     if (!_initialised) {
      88           0 :         return false;
      89             :     }
      90             : 
      91             :     // unhealthy if attitude information NOT received recently
      92        2105 :     const uint32_t now_ms = AP_HAL::millis();
      93        2105 :     if (now_ms - _last_current_angle_rad_ms > AP_MOUNT_VIEWPRO_HEALTH_TIMEOUT_MS) {
      94           0 :         return false;
      95             :     }
      96             : 
      97             :     // if we get this far return healthy
      98        2105 :     return true;
      99             : }
     100             : 
     101             : // get attitude as a quaternion.  returns true on success
     102        3083 : bool AP_Mount_Viewpro::get_attitude_quaternion(Quaternion& att_quat)
     103             : {
     104        3083 :     att_quat.from_euler(_current_angle_rad.x, _current_angle_rad.y, _current_angle_rad.z);
     105        3083 :     return true;
     106             : }
     107             : 
     108             : // reading incoming packets from gimbal and confirm they are of the correct format
     109             : // results are held in the _parsed_msg structure
     110        2056 : void AP_Mount_Viewpro::read_incoming_packets()
     111             : {
     112             :     // check for bytes on the serial port
     113        2056 :     int16_t nbytes = MIN(_uart->available(), 1024U);
     114        2056 :     if (nbytes <= 0 ) {
     115           0 :         return;
     116             :     }
     117             : 
     118             :     // flag to allow cases below to reset parser state
     119        2056 :     bool reset_parser = false;
     120             : 
     121             :     // process bytes received
     122      196387 :     for (int16_t i = 0; i < nbytes; i++) {
     123             :         uint8_t b;
     124      194331 :         if (!_uart->read(b)) {
     125           0 :             continue;
     126             :         }
     127             : 
     128      194331 :         _msg_buff[_msg_buff_len++] = b;
     129             : 
     130             :         // protect against overly long messages
     131      194331 :         if (_msg_buff_len > AP_MOUNT_VIEWPRO_PACKETLEN_MAX) {
     132           0 :             reset_parser = true;
     133             :             debug("vp buff full s:%u len:%u", (unsigned)_parsed_msg.state, (unsigned)_msg_buff_len);
     134             :         }
     135             : 
     136             :         // process byte depending upon current state
     137      194331 :         switch (_parsed_msg.state) {
     138             : 
     139        4136 :         case ParseState::WAITING_FOR_HEADER1:
     140        4136 :             if (b == AP_MOUNT_VIEWPRO_HEADER1) {
     141             :                 // throw away byte
     142        4136 :                 _msg_buff_len = 0;
     143        4136 :                 _parsed_msg.state = ParseState::WAITING_FOR_HEADER2;
     144             :             } else {
     145           0 :                 reset_parser = true;
     146             :             }
     147        4136 :             break;
     148             : 
     149        4136 :         case ParseState::WAITING_FOR_HEADER2:
     150        4136 :             if (b == AP_MOUNT_VIEWPRO_HEADER2) {
     151             :                 // throw away byte
     152        4136 :                 _msg_buff_len = 0;
     153        4136 :                 _parsed_msg.state = ParseState::WAITING_FOR_HEADER3;
     154             :             } else {
     155           0 :                 reset_parser = true;
     156             :             }
     157        4136 :             break;
     158             : 
     159        4136 :         case ParseState::WAITING_FOR_HEADER3:
     160        4136 :             if (b == AP_MOUNT_VIEWPRO_HEADER3) {
     161             :                 // throw away byte
     162        4136 :                 _msg_buff_len = 0;
     163        4136 :                 _parsed_msg.state = ParseState::WAITING_FOR_LENGTH;
     164             :             } else {
     165           0 :                 reset_parser = true;
     166             :             }
     167        4136 :             break;
     168             : 
     169        4136 :         case ParseState::WAITING_FOR_LENGTH:
     170             :             // length held in bits 0 ~ 5.  length includes this length byte, frame id and final crc
     171             :             // ignore frame counter held in bits 6~7
     172        4136 :             _parsed_msg.data_len = b & 0x3F;
     173        4136 :             _parsed_msg.state = ParseState::WAITING_FOR_FRAMEID;
     174        4136 :             break;
     175             : 
     176        4136 :         case ParseState::WAITING_FOR_FRAMEID:
     177        4136 :             _parsed_msg.frame_id = b;
     178        4136 :             _parsed_msg.data_bytes_received = 0;
     179        4136 :             _parsed_msg.state = ParseState::WAITING_FOR_DATA;
     180        4136 :             break;
     181             : 
     182      169515 :         case ParseState::WAITING_FOR_DATA:
     183      169515 :             _parsed_msg.data_bytes_received++;
     184             :             // check if we have received all data bytes.  subtract 3 to remove length byte, frame id and final crc
     185      169515 :             if (_parsed_msg.data_bytes_received >= _parsed_msg.data_len - 3) {
     186        4136 :                 _parsed_msg.state = ParseState::WAITING_FOR_CRC;
     187             :             }
     188      169515 :             break;
     189             : 
     190        4136 :         case ParseState::WAITING_FOR_CRC: {
     191        4136 :             _parsed_msg.crc = b;
     192        4136 :             const uint16_t expected_crc = calc_crc(_msg_buff, _msg_buff_len-1);
     193        4136 :             if (expected_crc == _parsed_msg.crc) {
     194             :                 // successfully received a message, do something with it
     195        4136 :                 process_packet();
     196             :             } else {
     197             :                 debug("crc expected:%x got:%x", (unsigned)expected_crc, (unsigned)_parsed_msg.crc);
     198             :             }
     199        4136 :             reset_parser = true;
     200        4136 :             break;
     201             :             }
     202             :         }
     203             : 
     204             :         // handle reset of parser
     205      194331 :         if (reset_parser) {
     206        4136 :             _msg_buff_len = 0;
     207        4136 :             _parsed_msg.state = ParseState::WAITING_FOR_HEADER1;
     208        4136 :             reset_parser = false;
     209             :         }
     210             :     }
     211             : }
     212             : 
     213             : // process successfully decoded packets held in the _parsed_msg structure
     214        4136 : void AP_Mount_Viewpro::process_packet()
     215             : {
     216             :     // process packet depending upon frame id
     217        4136 :     switch ((FrameId)_parsed_msg.frame_id) {
     218             : 
     219           0 :     case FrameId::HANDSHAKE:
     220           0 :         break;
     221             : 
     222           2 :     case FrameId::V: {
     223           2 :         const CommConfigCmd control_cmd = (CommConfigCmd)_msg_buff[_msg_buff_data_start];
     224             :         switch (control_cmd) {
     225           1 :         case CommConfigCmd::QUERY_FIRMWARE_VER: {
     226             :             // firmware version, length is 20 bytes but we expect format of "S" + yyyymmdd
     227           1 :             const uint8_t fw_major_str[3] {_msg_buff[_msg_buff_data_start+4], _msg_buff[_msg_buff_data_start+5], 0x0};
     228           1 :             const uint8_t fw_minor_str[3] {_msg_buff[_msg_buff_data_start+6], _msg_buff[_msg_buff_data_start+7], 0x0};
     229           1 :             const uint8_t fw_patch_str[3] {_msg_buff[_msg_buff_data_start+8], _msg_buff[_msg_buff_data_start+9], 0x0};
     230           1 :             const uint8_t major_ver = atoi((const char*)fw_major_str) & 0xFF;
     231           1 :             const uint8_t minor_ver = atoi((const char*)fw_minor_str) & 0xFF;
     232           1 :             const uint8_t patch_ver = atoi((const char*)fw_patch_str) & 0xFF;
     233           1 :             _firmware_version = (patch_ver << 16) | (minor_ver << 8) | major_ver;
     234           1 :             _got_firmware_version = true;
     235           1 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "%s fw:%u.%u.%u", send_text_prefix, (unsigned)major_ver, (unsigned)minor_ver, (unsigned)patch_ver);
     236           1 :             break;
     237             :         }
     238           1 :         case CommConfigCmd::QUERY_MODEL:
     239           1 :             if (_parsed_msg.data_bytes_received == 0) {
     240           0 :                 break;
     241             :             }
     242             :             // gimbal model, length is 10 bytes
     243           1 :             memset(_model_name, '\0', sizeof(_model_name));
     244           1 :             memcpy(_model_name, &_msg_buff[_msg_buff_data_start+1], MIN(sizeof(_model_name)-1, (size_t)(_parsed_msg.data_bytes_received-1)));
     245           1 :             _got_model_name = true;
     246           1 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "%s %s", send_text_prefix, _model_name);
     247           1 :             break;
     248           0 :         default:
     249             :             // unsupported control command
     250           0 :             break;
     251             :         }
     252           2 :         break;
     253             :     }
     254             : 
     255        4134 :     case FrameId::T1_F1_B1_D1: {
     256             :         // T1 holds target info including target lean angles
     257             :         // F1 holds tracker sensor status (which camera, tracking vs lost)
     258             :         // B1 section holds actual lean angles
     259             :         // D1 section holds camera status including zoom level
     260             :         //const int8_t servo_status = (_msg_buff[_msg_buff_data_start+24] & 0xF0) >> 4;
     261        4134 :         const TrackingStatus tracking_status = (TrackingStatus)((_msg_buff[_msg_buff_data_start+22] & 0x18) >> 3);
     262        4134 :         if (tracking_status != _last_tracking_status) {
     263           0 :             _last_tracking_status = tracking_status;
     264             :             switch (tracking_status) {
     265           0 :             case TrackingStatus::STOPPED:
     266           0 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "%s tracking OFF", send_text_prefix);
     267           0 :                 break;
     268           0 :             case TrackingStatus::SEARCHING:
     269           0 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "%s tracking searching", send_text_prefix);
     270           0 :                 break;
     271           0 :             case TrackingStatus::TRACKING:
     272           0 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "%s tracking ON", send_text_prefix);
     273           0 :                 break;
     274           0 :             case TrackingStatus::LOST:
     275           0 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "%s tracking Lost", send_text_prefix);
     276           0 :                 break;
     277             :             }
     278             :         }
     279             : 
     280        4134 :         _last_current_angle_rad_ms = AP_HAL::millis();
     281        4134 :         _current_angle_rad.x = radians((int16_t)UINT16_VALUE(_msg_buff[_msg_buff_data_start+23] & 0x0F, _msg_buff[_msg_buff_data_start+24]) * (180.0/4095.0) - 90.0);   // roll angle
     282        4134 :         _current_angle_rad.z = radians((int16_t)UINT16_VALUE(_msg_buff[_msg_buff_data_start+25], _msg_buff[_msg_buff_data_start+26]) * AP_MOUNT_VIEWPRO_OUTPUT_TO_DEG); // yaw angle
     283        4134 :         _current_angle_rad.y = -radians((int16_t)UINT16_VALUE(_msg_buff[_msg_buff_data_start+27], _msg_buff[_msg_buff_data_start+28]) * AP_MOUNT_VIEWPRO_OUTPUT_TO_DEG); // pitch angle
     284             :         debug("r:%4.1f p:%4.1f y:%4.1f", (double)degrees(_current_angle_rad.x), (double)degrees(_current_angle_rad.y), (double)degrees(_current_angle_rad.z));
     285             : 
     286             :         // get active image sensor. D1's image sensor values are one value lower than C1's
     287        4134 :         _image_sensor = ImageSensor((_msg_buff[_msg_buff_data_start+29] & 0x07) + 1);
     288             : 
     289             :         // get recording status
     290        4134 :         const RecordingStatus recording_status = (RecordingStatus)(_msg_buff[_msg_buff_data_start+32] & 0x07);
     291        4134 :         const bool recording = (recording_status == RecordingStatus::RECORDING);
     292        4134 :         if (recording != _recording) {
     293           0 :             _recording = recording;
     294           0 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO,  "%s recording %s", send_text_prefix, _recording ? "ON" : "OFF");
     295             :         }
     296             : 
     297        4134 :         switch (_image_sensor) {
     298        4134 :             default:   
     299             :             case ImageSensor::EO1:
     300             :             case ImageSensor::EO1_IR_PIP:
     301             :                 //optical zoom times
     302        4134 :                 _zoom_times = UINT16_VALUE(_msg_buff[_msg_buff_data_start+39], _msg_buff[_msg_buff_data_start+40]) * 0.1;
     303        4134 :                 break;
     304           0 :             case ImageSensor::IR:
     305             :             case ImageSensor::IR_EO1_PIP:
     306             :                 //ir zoom times
     307           0 :                 _zoom_times = ((_msg_buff[_msg_buff_data_start+29] >> 3) & 0x0F) + 1;
     308           0 :                 break;
     309             :         }        
     310             :         // get laser rangefinder distance
     311        4134 :         _rangefinder_dist_m = UINT16_VALUE(_msg_buff[_msg_buff_data_start+33], _msg_buff[_msg_buff_data_start+34]) * 0.1;
     312        4134 :         break;
     313             :     }
     314             : 
     315           0 :     default:
     316             :         debug("Unhandled FrameId:%u", (unsigned)_parsed_msg.frame_id);
     317           0 :         break;
     318             :     }
     319        4136 : }
     320             : 
     321             : // calculate crc of the received message
     322       10230 : uint8_t AP_Mount_Viewpro::calc_crc(const uint8_t *buf, uint8_t len) const
     323             : {
     324       10230 :     uint8_t res = 0;
     325      303945 :     for (uint8_t i=0; i<len; i++) {
     326      293715 :         res = (res ^ buf[i]) & 0xFF;
     327             :     }
     328       10230 :     return res;
     329             : }
     330             : 
     331             : // calculate the length and frame count byte (3rd byte of all messages)
     332             : // length is all bytes after the header including CRC
     333        6094 : uint8_t AP_Mount_Viewpro::get_length_and_frame_count_byte(uint8_t length)
     334             : {
     335             :   // increment frame counter
     336        6094 :   _last_frame_counter = (_last_frame_counter + 1) & 0x03;
     337        6094 :   return ((_last_frame_counter << 6) | (length & 0x3F));
     338             : }
     339             : 
     340             : // send packet to gimbal.  databuff includes everything after the length-and-frame-counter, does not include crc
     341             : // returns true on success, false if outgoing serial buffer is full
     342        6094 : bool AP_Mount_Viewpro::send_packet(const uint8_t* databuff, uint8_t databuff_len)
     343             : {
     344        6094 :     if (!_initialised) {
     345           0 :         return false;
     346             :     }
     347             : 
     348             :     // calculate and sanity check packet size
     349        6094 :     const uint16_t packet_size = AP_MOUNT_VIEWPRO_PACKETLEN_MIN + databuff_len;
     350        6094 :     if (packet_size > AP_MOUNT_VIEWPRO_PACKETLEN_MAX) {
     351             :         debug("send_packet data buff too large");
     352           0 :         return false;
     353             :     }
     354             : 
     355             :     // check for sufficient space in outgoing buffer
     356        6094 :     if (_uart->txspace() < packet_size) {
     357             :         debug("tx space too low (%u < %u)", (unsigned)_uart->txspace(), (unsigned)packet_size);
     358           0 :         return false;
     359             :     }
     360             : 
     361             :     // buffer for holding outgoing packet
     362        6094 :     uint8_t send_buff[packet_size];
     363        6094 :     uint8_t send_buff_ofs = 0;
     364             : 
     365             :     // packet header
     366        6094 :     send_buff[send_buff_ofs++] = AP_MOUNT_VIEWPRO_HEADER1;
     367        6094 :     send_buff[send_buff_ofs++] = AP_MOUNT_VIEWPRO_HEADER2;
     368        6094 :     send_buff[send_buff_ofs++] = AP_MOUNT_VIEWPRO_HEADER3;
     369             : 
     370             :     // length and frame counter. length is databuffer length + 2 (1 for length, 1 for crc)
     371        6094 :     send_buff[send_buff_ofs++] = get_length_and_frame_count_byte(databuff_len + 2);
     372             : 
     373             :     // data
     374        6094 :     if (databuff_len != 0) {
     375        6094 :         memcpy(&send_buff[send_buff_ofs], databuff, databuff_len);
     376        6094 :         send_buff_ofs += databuff_len;
     377             :     }
     378             : 
     379             :     // crc
     380        6094 :     const uint8_t crc = calc_crc(&send_buff[3], send_buff_ofs-3);
     381        6094 :     send_buff[send_buff_ofs++] = crc;
     382             : 
     383             :     // write packet to serial port
     384        6094 :     _uart->write(send_buff, send_buff_ofs);
     385             :     
     386             : 
     387        6094 :     return true;
     388        6094 : }
     389             : 
     390             : // send handshake, gimbal will respond with T1_F1_B1_D1 paket that includes current angles
     391        2056 : void AP_Mount_Viewpro::send_handshake()
     392             : {
     393        2056 :     const HandshakePacket hs_packet {
     394             :         .content = {
     395             :             frame_id: FrameId::HANDSHAKE,
     396             :             unused: 0
     397             :         }
     398             :     };
     399        2056 :     send_packet(hs_packet.bytes, sizeof(hs_packet.bytes));
     400        2056 : }
     401             : 
     402             : // set gimbal's lock vs follow mode
     403             : // lock should be true if gimbal should maintain an earth-frame target
     404             : // lock is false to follow / maintain a body-frame target
     405        2056 : bool AP_Mount_Viewpro::set_lock(bool lock)
     406             : {
     407             :     // do not send if lock mode has already been sent recently
     408        2056 :     if (_last_lock == lock) {
     409        2056 :         return true;
     410             :     }
     411             : 
     412             :     // fill in packet
     413           0 :     const A1Packet a1_packet {
     414             :         .content = {
     415             :             frame_id: FrameId::A1,
     416           0 :             servo_status: lock ? ServoStatus::FOLLOW_YAW_DISABLE : ServoStatus::FOLLOW_YAW
     417             :         }
     418           0 :     };
     419             : 
     420             :     // send targets to gimbal
     421           0 :     if (send_packet(a1_packet.bytes, sizeof(a1_packet.bytes))) {
     422           0 :         _last_lock = lock;
     423           0 :         return true;
     424             :     }
     425           0 :     return false;
     426             : }
     427             : 
     428             : // send communication configuration command (aka U packet), gimbal will respond with a V packet
     429           2 : bool AP_Mount_Viewpro::send_comm_config_cmd(CommConfigCmd cmd)
     430             : {
     431             :     // fill in packet
     432           2 :     const UPacket u_packet {
     433             :         .content = {
     434             :             frame_id: FrameId::U,
     435             :             control_cmd: cmd
     436             :         }
     437           2 :     };
     438             : 
     439             :     // send targets to gimbal
     440           2 :     return send_packet(u_packet.bytes, sizeof(u_packet.bytes));
     441             : }
     442             : 
     443             : // send target pitch and yaw rates to gimbal
     444           0 : void AP_Mount_Viewpro::send_target_rates(const MountRateTarget &rate_rads)
     445             : {
     446           0 :     const float pitch_rads = rate_rads.pitch;
     447           0 :     const float yaw_rads = rate_rads.yaw;
     448           0 :     const bool yaw_is_ef = rate_rads.yaw_is_ef;
     449             : 
     450             :     // set lock value
     451           0 :     if (!set_lock(yaw_is_ef)) {
     452           0 :         return;
     453             :     }
     454             : 
     455             :     // scale pitch and yaw to values gimbal understands
     456           0 :     const int16_t pitch_rate_output = -degrees(pitch_rads) * 100.0;
     457           0 :     const int16_t yaw_rate_output = degrees(yaw_rads) * 100.0;
     458             : 
     459             :     // fill in packet
     460           0 :     const A1Packet a1_packet {
     461             :         .content = {
     462             :             frame_id: FrameId::A1,
     463             :             servo_status: ServoStatus::MANUAL_SPEED_MODE,
     464           0 :             yaw_be: htobe16(yaw_rate_output),
     465           0 :             pitch_be: htobe16(pitch_rate_output)
     466             :         }
     467           0 :     };
     468             : 
     469             :     // send targets to gimbal
     470           0 :     send_packet(a1_packet.bytes, sizeof(a1_packet.bytes));
     471             : }
     472             : 
     473             : // send target pitch and yaw angles to gimbal
     474        2056 : void AP_Mount_Viewpro::send_target_angles(const MountAngleTarget &angle_rad)
     475             : {
     476        2056 :     const float pitch_rad = angle_rad.pitch;
     477        2056 :     const float yaw_rad = angle_rad.yaw;
     478             : 
     479             :     // gimbal does not support lock in angle control mode
     480        2056 :     if (!set_lock(false)) {
     481           0 :         return;
     482             :     }
     483             : 
     484             :     // convert yaw angle to body-frame
     485        2056 :     float yaw_bf_rad = angle_rad.yaw_is_ef ? wrap_PI(yaw_rad - AP::ahrs().get_yaw_rad()) : yaw_rad;
     486             : 
     487             :     // enforce body-frame yaw angle limits.  If beyond limits always use body-frame control
     488        2056 :     const float yaw_bf_min = radians(_params.yaw_angle_min);
     489        2056 :     const float yaw_bf_max = radians(_params.yaw_angle_max);
     490        2056 :     if (yaw_bf_rad < yaw_bf_min || yaw_bf_rad > yaw_bf_max) {
     491           0 :         yaw_bf_rad = constrain_float(yaw_bf_rad, yaw_bf_min, yaw_bf_max);
     492             :     }
     493             : 
     494             :     // scale pitch and yaw to values gimbal understands
     495        2056 :     const int16_t pitch_angle_output = -pitch_rad * AP_MOUNT_VIEWPRO_RAD_TO_OUTPUT;
     496        2056 :     const int16_t yaw_angle_output = yaw_bf_rad * AP_MOUNT_VIEWPRO_RAD_TO_OUTPUT;
     497             : 
     498             :     // fill in packet
     499        2056 :     const A1Packet a1_packet {
     500             :         .content = {
     501             :             frame_id: FrameId::A1,
     502             :             servo_status: ServoStatus::MANUAL_ABSOLUTE_ANGLE_MODE,
     503        2056 :             yaw_be: htobe16(yaw_angle_output),
     504        2056 :             pitch_be: htobe16(pitch_angle_output)
     505             :         }
     506        2056 :     };
     507             : 
     508             :     // send targets to gimbal
     509        2056 :     send_packet(a1_packet.bytes, sizeof(a1_packet.bytes));
     510             : }
     511             : 
     512             : // send camera command, affected image sensor and value (e.g. zoom speed)
     513           0 : bool AP_Mount_Viewpro::send_camera_command(ImageSensor img_sensor, CameraCommand cmd, uint8_t value, LRFCommand lrf_cmd)
     514             : {
     515             :     // fill in 2 bytes containing sensor, zoom speed, operation command and LRF
     516             :     // bit0~2: sensor
     517             :     // bit3~5: zoom speed
     518             :     // bit6~12: operation command no
     519             :     // bit13~15: LRF command
     520           0 :     const uint16_t sensor_id = (uint16_t)img_sensor;
     521           0 :     const uint16_t zoom_speed = ((uint16_t)value & 0x07) << 3;
     522           0 :     const uint16_t operation_cmd = ((uint16_t)cmd & 0x7F) << 6;
     523           0 :     const uint16_t rangefinder_cmd = ((uint16_t)lrf_cmd & 0x07) << 13;
     524             : 
     525             :     // fill in packet
     526           0 :     const C1Packet c1_packet {
     527             :         .content = {
     528             :             frame_id: FrameId::C1,
     529           0 :             sensor_zoom_cmd_be:  htobe16(sensor_id | zoom_speed | operation_cmd | rangefinder_cmd)
     530             :         }
     531           0 :     };
     532             : 
     533             :     // send packet to gimbal
     534           0 :     return send_packet(c1_packet.bytes, sizeof(c1_packet.bytes));
     535             : }
     536             : 
     537             : // send camera command2 and corresponding value (e.g. zoom as absolute value)
     538           0 : bool AP_Mount_Viewpro::send_camera_command2(CameraCommand2 cmd, uint16_t value)
     539             : {
     540             :     // fill in packet
     541           0 :     const C2Packet c2_packet {
     542             :         .content = {
     543             :             frame_id: FrameId::C2,
     544             :             cmd: cmd,
     545           0 :             value_be: htobe16(value)
     546             :         }
     547           0 :     };
     548             : 
     549             :     // send packet to gimbal
     550           0 :     return send_packet(c2_packet.bytes, sizeof(c2_packet.bytes));
     551             : }
     552             : 
     553             : // send tracking command and corresponding value (normally zero)
     554           0 : bool AP_Mount_Viewpro::send_tracking_command(TrackingCommand cmd, uint8_t value)
     555             : {
     556             :     // convert image sensor to tracking source
     557           0 :     TrackingSource tracking_source = TrackingSource::EO1;
     558           0 :     switch (_image_sensor) {
     559           0 :     case ImageSensor::NO_ACTION:    
     560             :     case ImageSensor::EO1:
     561             :     case ImageSensor::EO1_IR_PIP:
     562             :     case ImageSensor::FUSION:
     563           0 :         tracking_source = TrackingSource::EO1;
     564           0 :         break;
     565           0 :     case ImageSensor::IR:
     566             :     case ImageSensor::IR_EO1_PIP:
     567             :     case ImageSensor::IR1_13MM:
     568             :     case ImageSensor::IR2_52MM:
     569           0 :         tracking_source = TrackingSource::IR;
     570           0 :         break;    
     571             :     }
     572             : 
     573             :     // fill in packet
     574             :     // Packet creation is done long-hand here to support g++-7.5.0
     575           0 :     E1Packet e1_packet {};
     576           0 :     e1_packet.content.frame_id = FrameId::E1;
     577           0 :     e1_packet.content.source = tracking_source;
     578           0 :     e1_packet.content.cmd = cmd;
     579           0 :     e1_packet.content.param2 = value; // normally zero
     580             : 
     581             :     // send packet to gimbal
     582           0 :     return send_packet(e1_packet.bytes, sizeof(e1_packet.bytes));
     583             : }
     584             : 
     585             : // send camera command2 and corresponding parameter values
     586           0 : bool AP_Mount_Viewpro::send_tracking_command2(TrackingCommand2 cmd, int16_t param1, int16_t param2)
     587             : {
     588             :     // fill in packet
     589           0 :     const E2Packet e2_packet {
     590             :         .content = {
     591             :             frame_id: FrameId::E2,
     592             :             cmd: cmd,
     593           0 :             param1_be: htobe16(param1),
     594           0 :             param2_be: htobe16(param2),
     595             :         }
     596           0 :     };
     597             : 
     598             :     // send packet to gimbal
     599           0 :     return send_packet(e2_packet.bytes, sizeof(e2_packet.bytes));
     600             : }
     601             : 
     602             : // send vehicle attitude and position to gimbal
     603        2056 : bool AP_Mount_Viewpro::send_m_ahrs()
     604             : {
     605             :     // get current location
     606        2056 :     Location loc;
     607        2056 :     int32_t alt_amsl_cm = 0;
     608        2056 :     if (!AP::ahrs().get_location(loc) || !loc.get_alt_cm(Location::AltFrame::ABSOLUTE, alt_amsl_cm)) {
     609          76 :         return false;
     610             :     }
     611             : 
     612             : #if AP_RTC_ENABLED
     613             :     // get date and time
     614             :     uint16_t year, ms;
     615             :     uint8_t month, day, hour, min, sec;
     616        1980 :     if (!AP::rtc().get_date_and_time_utc(year, month, day, hour, min, sec, ms)) {
     617           0 :         year = month = day = hour = min = sec = ms = 0;
     618             :     }
     619        1980 :     uint16_t date = ((year-2000) & 0x7f) | (((month+1) & 0x0F) << 7) | ((day & 0x1F) << 11);
     620        1980 :     uint64_t second_hundredths = (((hour * 60 * 60) + (min * 60) + sec) * 100) + (ms * 0.1);
     621             : #else
     622             :     const uint16_t date = 0;
     623             :     const uint64_t second_hundredths = 0;
     624             : #endif
     625             : 
     626             :     // get vehicle velocity in m/s in NED Frame
     627        1980 :     Vector3f vel_NED;
     628        1980 :     IGNORE_RETURN(AP::ahrs().get_velocity_NED(vel_NED));
     629        1980 :     const float vel_yaw_rad = wrap_2PI(vel_NED.xy().angle());
     630             : 
     631             :     // get GPS vdop
     632        1980 :     uint16_t gps_vdop = AP::gps().get_vdop();
     633             : 
     634             :     // get vehicle yaw in the range 0 to 2*pi
     635        1980 :     const float veh_yaw_rad = wrap_2PI(AP::ahrs().get_yaw_rad());
     636             : 
     637             :     // fill in packet
     638        1980 :     const M_AHRSPacket mahrs_packet {
     639             :         .content = {
     640             :             frame_id: FrameId::M_AHRS,
     641             :             data_type: 0x07,                        // Bit0: Attitude, Bit1: GPS, Bit2 Gyro
     642             :             unused2to8 : {0, 0, 0, 0, 0, 0, 0},
     643        1980 :             roll_be: htobe16(AP::ahrs().get_roll_rad() * AP_MOUNT_VIEWPRO_RAD_TO_OUTPUT),      // vehicle roll angle.  1bit=360deg/65536
     644        1980 :             pitch_be: htobe16(-AP::ahrs().get_pitch_rad() * AP_MOUNT_VIEWPRO_RAD_TO_OUTPUT),   // vehicle pitch angle.  1bit=360deg/65536
     645        1980 :             yaw_be: htobe16(veh_yaw_rad * AP_MOUNT_VIEWPRO_RAD_TO_OUTPUT),                          // vehicle yaw angle.  1bit=360deg/65536
     646        1980 :             date_be: htobe16(date),                 // bit0~6:year, bit7~10:month, bit11~15:day
     647        1980 :             seconds_utc: {uint8_t((second_hundredths & 0xFF0000ULL) >> 16), // seconds * 100 MSB.  1bit = 0.01sec
     648        1980 :                           uint8_t((second_hundredths & 0xFF00ULL) >> 8),    // seconds * 100 next MSB.  1bit = 0.01sec
     649             :                           uint8_t(second_hundredths & 0xFFULL)},            // seconds * 100 LSB.  1bit = 0.01sec
     650        1980 :             gps_yaw_be: htobe16(vel_yaw_rad * AP_MOUNT_VIEWPRO_RAD_TO_OUTPUT),  // GPS yaw
     651             :             position_mark_bitmask: 0x0F,            // bit0:new position, bit1:clock fix calced, bit2:horiz calced, bit3:alt calced
     652        1980 :             latitude_be: htobe32(loc.lat),          // latitude.  1bit = 10e-7
     653        1980 :             longitude_be: htobe32(loc.lng),         // longitude.  1bit = 10e-7
     654        1980 :             height_be: htobe32(alt_amsl_cm * 10),   // height.  1bit = 1mm
     655        1980 :             ground_speed_N_be: htobe16(vel_NED.x * 100),    // ground speed in North direction. 1bit = 0.01m/s
     656        1980 :             ground_speed_E_be: htobe16(vel_NED.y * 100),    // ground speed in East direction. 1bit = 0.01m/s
     657        1980 :             vdop_be: htobe16(gps_vdop),             // GPS vdop. 1bit = 0.01
     658        1980 :             ground_speed_D_be: htobe16(vel_NED.z * 100)     // speed downwards. 1bit = 0.01m/s
     659             :         }
     660        3960 :     };
     661             : 
     662             :     // send packet to gimbal
     663        1980 :     return send_packet(mahrs_packet.bytes, sizeof(mahrs_packet.bytes));
     664             : }
     665             : 
     666             : // take a picture.  returns true on success
     667           0 : bool AP_Mount_Viewpro::take_picture()
     668             : {
     669             :     // exit immediately if not initialised
     670           0 :     if (!_initialised) {
     671           0 :         return false;
     672             :     }
     673             : 
     674           0 :     return send_camera_command(_image_sensor, CameraCommand::TAKE_PICTURE, 0);
     675             : }
     676             : 
     677             : // start or stop video recording.  returns true on success
     678             : // set start_recording = true to start record, false to stop recording
     679           0 : bool AP_Mount_Viewpro::record_video(bool start_recording)
     680             : {
     681             :     // exit immediately if not initialised
     682           0 :     if (!_initialised) {
     683           0 :         return false;
     684             :     }
     685             : 
     686           0 :     return send_camera_command(_image_sensor, start_recording ? CameraCommand::START_RECORD : CameraCommand::STOP_RECORD, 0);
     687             : }
     688             : 
     689             : // set zoom specified as a rate or percentage
     690           0 : bool AP_Mount_Viewpro::set_zoom(ZoomType zoom_type, float zoom_value)
     691             : {
     692             :     // exit immediately if not initialised
     693           0 :     if (!_initialised) {
     694           0 :         return false;
     695             :     }
     696             : 
     697             :     // zoom rate
     698           0 :     if (zoom_type == ZoomType::RATE) {
     699           0 :         uint8_t zoom_speed = 1;
     700           0 :         CameraCommand zoom_cmd = CameraCommand::STOP_FOCUS_AND_ZOOM;
     701             :         
     702           0 :         switch (_image_sensor) {
     703           0 :             default:   
     704             :             case ImageSensor::EO1:
     705             :             case ImageSensor::EO1_IR_PIP:
     706           0 :                 if (zoom_value < 0) {
     707           0 :                     zoom_cmd = CameraCommand::ZOOM_OUT;
     708           0 :                 } else if (zoom_value > 0) {
     709           0 :                     zoom_cmd = CameraCommand::ZOOM_IN;
     710             :                 }
     711           0 :                 zoom_speed = AP_MOUNT_VIEWPRO_EO_ZOOM_SPEED;
     712           0 :                 break;
     713           0 :             case ImageSensor::IR:
     714             :             case ImageSensor::IR_EO1_PIP:
     715           0 :                 if (zoom_value < 0) {
     716           0 :                     zoom_cmd = CameraCommand::IR_ZOOM_IN;
     717           0 :                 } else if (zoom_value > 0) {
     718           0 :                     zoom_cmd = CameraCommand::IR_ZOOM_OUT;
     719             :                 }
     720           0 :                 zoom_speed = AP_MOUNT_VIEWPRO_IR_ZOOM_SPEED;
     721           0 :                 break;    
     722             :         }
     723             :         
     724           0 :         return send_camera_command(_image_sensor, zoom_cmd, zoom_speed);
     725             :     }
     726             : 
     727             :     // zoom percentage
     728           0 :     if (zoom_type == ZoomType::PCT) {
     729             :         // convert zoom percentage (0 ~ 100) to zoom value (0 ~ max zoom * 10)
     730           0 :         return send_camera_command2(CameraCommand2::SET_EO_ZOOM, linear_interpolate(0, AP_MOUNT_VIEWPRO_ZOOM_MAX * 10, zoom_value, 0, 100));
     731             :     }
     732             : 
     733             :     // unsupported zoom type
     734           0 :     return false;
     735             : }
     736             : 
     737             : // set focus specified as rate, percentage or auto
     738             : // focus in = -1, focus hold = 0, focus out = 1
     739           0 : SetFocusResult AP_Mount_Viewpro::set_focus(FocusType focus_type, float focus_value)
     740             : {
     741             :     // exit immediately if not initialised
     742           0 :     if (!_initialised) {
     743           0 :         return SetFocusResult::FAILED;
     744             :     }
     745             : 
     746           0 :     switch (focus_type) {
     747           0 :     case FocusType::RATE: {
     748           0 :         CameraCommand focus_cmd = CameraCommand::STOP_FOCUS_AND_ZOOM;
     749           0 :         if (focus_value < 0) {
     750           0 :             focus_cmd = CameraCommand::FOCUS_MINUS;
     751           0 :         } else if (focus_value > 0) {
     752           0 :             focus_cmd = CameraCommand::FOCUS_PLUS;
     753             :         }
     754           0 :         if (!send_camera_command(_image_sensor, focus_cmd, 0)) {
     755           0 :             return SetFocusResult::FAILED;
     756             :         }
     757           0 :         return SetFocusResult::ACCEPTED;
     758             :     }
     759           0 :     case FocusType::PCT:
     760             :         // not supported
     761           0 :         return SetFocusResult::INVALID_PARAMETERS;
     762           0 :     case FocusType::AUTO:
     763           0 :         if (!send_camera_command(_image_sensor, CameraCommand::AUTO_FOCUS, 0)) {
     764           0 :             return SetFocusResult::FAILED;
     765             :         }
     766           0 :         return SetFocusResult::ACCEPTED;
     767             :     }
     768             : 
     769             :     // unsupported focus type
     770           0 :     return SetFocusResult::INVALID_PARAMETERS;
     771             : }
     772             : 
     773             : // set tracking to none, point or rectangle (see TrackingType enum)
     774             : // if POINT only p1 is used, if RECTANGLE then p1 is top-left, p2 is bottom-right
     775             : // p1,p2 are in range 0 to 1.  0 is left or top, 1 is right or bottom
     776           0 : bool AP_Mount_Viewpro::set_tracking(TrackingType tracking_type, const Vector2f& p1, const Vector2f& p2)
     777             : {
     778             :     // exit immediately if not initialised
     779           0 :     if (!_initialised) {
     780           0 :         return false;
     781             :     }
     782             : 
     783           0 :     switch (tracking_type) {
     784           0 :     case TrackingType::TRK_NONE:
     785           0 :         return send_tracking_command(TrackingCommand::STOP, 0);
     786             :         break;
     787           0 :     case TrackingType::TRK_POINT: {
     788           0 :         return (send_tracking_command(TrackingCommand::START, 0) &&
     789           0 :                 send_tracking_command2(TrackingCommand2::SET_POINT, (p1.x - 0.5) * 2.0 * 960, (p1.y - 0.5) * 2.0 * 540));
     790             :         break;
     791             :     }
     792           0 :     case TrackingType::TRK_RECTANGLE:
     793           0 :         return (send_tracking_command(TrackingCommand::START, 0) &&
     794           0 :                 send_tracking_command2(TrackingCommand2::SET_RECT_TOPLEFT, (p1.x - 0.5) * 2.0 * 960, (p1.y - 0.5) * 2.0 * 540) &&
     795           0 :                 send_tracking_command2(TrackingCommand2::SET_RECT_BOTTOMRIGHT, (p2.x - 0.5) * 2.0 * 960, (p2.y - 0.5) * 2.0 * 540));
     796             :         break;
     797             :     }
     798             : 
     799             :     // should never reach here
     800           0 :     return false;
     801             : }
     802             : 
     803             : // set camera lens as a value from 0 to 5
     804           0 : bool AP_Mount_Viewpro::set_lens(uint8_t lens)
     805             : {
     806             :     // exit immediately if not initialised
     807           0 :     if (!_initialised) {
     808           0 :         return false;
     809             :     }
     810             : 
     811             :     // match lens to ImageSensor enum values and sanity check
     812           0 :     lens++;
     813           0 :     if (lens > (uint8_t)ImageSensor::IR2_52MM) {
     814           0 :         return false;
     815             :     }
     816             : 
     817             :     // if lens is zero use default lens
     818           0 :     ImageSensor new_image_sensor = ImageSensor(lens);
     819           0 :     return send_camera_command(new_image_sensor, CameraCommand::NO_ACTION, 0);
     820             : }
     821             : 
     822             : // set_camera_source is functionally the same as set_lens except primary and secondary lenses are specified by type
     823             : // primary and secondary sources use the AP_Camera::CameraSource enum cast to uint8_t
     824           0 : bool AP_Mount_Viewpro::set_camera_source(uint8_t primary_source, uint8_t secondary_source)
     825             : {
     826             :     // maps primary and secondary source to viewpro image sensor
     827             :     ImageSensor new_image_sensor;
     828           0 :     switch (primary_source) {
     829           0 :     case 0: // Default (RGB)
     830             :         FALLTHROUGH;
     831             :     case 1: // RGB
     832           0 :         switch (secondary_source) {
     833           0 :         case 0: // RGB + Default (None)
     834           0 :             new_image_sensor = ImageSensor::EO1;
     835           0 :             break;
     836           0 :         case 2: // PIP RGB+IR
     837           0 :             new_image_sensor = ImageSensor::EO1_IR_PIP;
     838           0 :             break;
     839           0 :         default:
     840           0 :             return false;
     841             :         }
     842           0 :         break;
     843           0 :     case 2: // IR
     844           0 :         switch (secondary_source) {
     845           0 :         case 0: // IR + Default (None)
     846           0 :             new_image_sensor = ImageSensor::IR;
     847           0 :             break;
     848           0 :         case 1: // PIP IR+RGB
     849           0 :             new_image_sensor = ImageSensor::IR_EO1_PIP;
     850           0 :             break;
     851           0 :         default:
     852           0 :             return false;
     853             :         }
     854           0 :         break;
     855           0 :     default:
     856           0 :         return false;
     857             :     }
     858             : 
     859             :     // send desired image type to camera
     860           0 :     return send_camera_command(new_image_sensor, CameraCommand::NO_ACTION, 0);
     861             : }
     862             : 
     863             : // send camera settings message to GCS
     864           0 : void AP_Mount_Viewpro::send_camera_settings(mavlink_channel_t chan) const
     865             : {
     866             :     // exit immediately if not initialised
     867           0 :     if (!_initialised) {
     868           0 :         return;
     869             :     }
     870             : 
     871             :     // convert zoom times (e.g. 1x ~ 20x) to target zoom level (e.g. 0 to 100)
     872           0 :     const float zoom_level = linear_interpolate(0, 100, _zoom_times, 1, AP_MOUNT_VIEWPRO_ZOOM_MAX);
     873             : 
     874             :     // send CAMERA_SETTINGS message
     875           0 :     mavlink_msg_camera_settings_send(
     876             :         chan,
     877             :         AP_HAL::millis(),   // time_boot_ms
     878           0 :         _recording ? CAMERA_MODE_VIDEO : CAMERA_MODE_IMAGE, // camera mode (0:image, 1:video, 2:image survey)
     879             :         zoom_level,         // zoomLevel float, percentage from 0 to 100, NaN if unknown
     880             :         NaNf);              // focusLevel float, percentage from 0 to 100, NaN if unknown
     881             : }
     882             : 
     883             : // get rangefinder distance.  Returns true on success
     884        2055 : bool AP_Mount_Viewpro::get_rangefinder_distance(float& distance_m) const
     885             : {
     886             :     // if not healthy or zero distance return false
     887             :     // healthy() checks attitude timeout which is in same message as rangefinder distance
     888        2055 :     if (!healthy()) {
     889           0 :         return false;
     890             :     }
     891             : 
     892        2055 :     distance_m = _rangefinder_dist_m;
     893        2055 :     return true;
     894             : }
     895             : 
     896             : // enable/disable rangefinder.  Returns true on success
     897           0 : bool AP_Mount_Viewpro::set_rangefinder_enable(bool enable)
     898             : {
     899           0 :     return send_camera_command(ImageSensor::NO_ACTION, CameraCommand::NO_ACTION, 0, enable ? LRFCommand::CONTINUOUS_RANGING_START : LRFCommand::STOP_RANGING);
     900             : }
     901             : 
     902             : #endif // HAL_MOUNT_VIEWPRO_ENABLED

Generated by: LCOV version 1.14