LCOV - code coverage report
Current view: top level - SITL - SIM_MAVLinkGimbalv2.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 161 218 73.9 %
Date: 2026-09-09 11:02:08 Functions: 10 10 100.0 %

          Line data    Source code
       1             : /*
       2             :    Simulator for MAVLink Gimbal Protocol v2 peripherals
       3             : */
       4             : 
       5             : #include "SIM_config.h"
       6             : 
       7             : #if AP_SIM_MAVLINKGIMBALV2_ENABLED
       8             : 
       9             : #include "SIM_MAVLinkGimbalv2.h"
      10             : #include "SIM_Aircraft.h"
      11             : #include <AP_HAL/AP_HAL.h>
      12             : #include <stdio.h>
      13             : 
      14             : namespace SITL {
      15             : 
      16           5 : void MAVLinkGimbalv2::set_instance(uint8_t instance)
      17             : {
      18           5 :     Mount::set_instance(instance);
      19             :     // select the component ID matching this gimbal instance:
      20             :     // instance 0 → MAV_COMP_ID_GIMBAL (154)
      21             :     // instance 1 → MAV_COMP_ID_GIMBAL2 (171), 2 → 172, …
      22           5 :     _compid = (instance == 0) ? MAV_COMP_ID_GIMBAL
      23             :                                : MAV_COMP_ID_GIMBAL2 + (instance - 1);
      24             : 
      25             :     // configure joint limits from subclass declarations (roll kept at ±40°)
      26           5 :     _gimbal.set_joint_limits(
      27           5 :         Vector3f(radians(-40.0f), get_pitch_min_rad(), get_yaw_min_rad()),
      28           5 :         Vector3f(radians( 40.0f), get_pitch_max_rad(), get_yaw_max_rad())
      29             :     );
      30           5 : }
      31             : 
      32        3326 : void MAVLinkGimbalv2::send_mavlink_message(const mavlink_message_t &msg)
      33             : {
      34             :     uint8_t buf[MAVLINK_MAX_PACKET_LEN];
      35        3326 :     const uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
      36        3326 :     if (len > 0) {
      37        3326 :         write_to_autopilot((char*)buf, len);
      38             :     }
      39        3326 : }
      40             : 
      41      332820 : void MAVLinkGimbalv2::update_input()
      42             : {
      43             :     uint8_t buf[128];
      44      332820 :     const ssize_t nread = read_from_autopilot((char*)buf, sizeof(buf));
      45     1828071 :     for (ssize_t i = 0; i < nread; i++) {
      46             :         mavlink_message_t msg;
      47             :         mavlink_status_t status;
      48     2990502 :         if (mavlink_frame_char_buffer(&mav.rxmsg, &mav.status,
      49     1495251 :                                       buf[i], &msg, &status) == MAVLINK_FRAMING_OK) {
      50       27617 :             handle_message(msg);
      51             :         }
      52             :     }
      53      332820 : }
      54             : 
      55       27617 : void MAVLinkGimbalv2::handle_message(const mavlink_message_t &msg)
      56             : {
      57       27617 :     switch (msg.msgid) {
      58        2055 :     case MAVLINK_MSG_ID_HEARTBEAT: {
      59        2055 :         if (!_seen_autopilot_heartbeat) {
      60           5 :             _seen_autopilot_heartbeat = true;
      61           5 :             _vehicle_system_id = msg.sysid;
      62           5 :             _vehicle_component_id = msg.compid;
      63           5 :             ::printf("MAVLinkGimbalv2[%u]: using sysid=%u compid=%u\n",
      64           5 :                      (unsigned)_instance,
      65           5 :                      (unsigned)_vehicle_system_id,
      66           5 :                      (unsigned)_compid);
      67             :         }
      68        2055 :         break;
      69             :     }
      70          37 :     case MAVLINK_MSG_ID_COMMAND_LONG: {
      71             :         mavlink_command_long_t cmd;
      72          37 :         mavlink_msg_command_long_decode(&msg, &cmd);
      73             :         // only handle messages addressed to this gimbal
      74          37 :         if (cmd.target_system != _vehicle_system_id ||
      75          37 :             cmd.target_component != _compid) {
      76             :             break;
      77             :         }
      78           4 :         if (cmd.command == MAV_CMD_REQUEST_MESSAGE) {
      79           4 :             const uint32_t requested_msgid = (uint32_t)cmd.param1;
      80           4 :             if (requested_msgid == MAVLINK_MSG_ID_GIMBAL_DEVICE_INFORMATION) {
      81           4 :                 send_gimbal_device_information();
      82           4 :                 send_command_ack(msg.sysid, msg.compid,
      83             :                                  MAV_CMD_REQUEST_MESSAGE, MAV_RESULT_ACCEPTED);
      84             :             }
      85             :         }
      86           4 :         break;
      87             :     }
      88           0 :     case MAVLINK_MSG_ID_COMMAND_INT: {
      89             :         mavlink_command_int_t cmd;
      90           0 :         mavlink_msg_command_int_decode(&msg, &cmd);
      91           0 :         if (cmd.target_system != _vehicle_system_id ||
      92           0 :             cmd.target_component != _compid) {
      93             :             break;
      94             :         }
      95           0 :         if (cmd.command == MAV_CMD_DO_SET_ROI_LOCATION) {
      96           0 :             _roi.loc.lat = cmd.x;
      97           0 :             _roi.loc.lng = cmd.y;
      98           0 :             _roi.loc.set_alt_m(cmd.z, (Location::AltFrame)cmd.frame);
      99           0 :             _roi.valid = true;
     100           0 :         } else if (cmd.command == MAV_CMD_DO_SET_ROI_NONE) {
     101           0 :             _roi.valid = false;
     102             :         }
     103           0 :         break;
     104             :     }
     105       11600 :     case MAVLINK_MSG_ID_GIMBAL_DEVICE_SET_ATTITUDE: {
     106             :         mavlink_gimbal_device_set_attitude_t cmd;
     107       11600 :         mavlink_msg_gimbal_device_set_attitude_decode(&msg, &cmd);
     108       11600 :         if (cmd.target_system != _vehicle_system_id ||
     109       11600 :             cmd.target_component != _compid) {
     110             :             break;
     111             :         }
     112       11600 :         const uint16_t flags = cmd.flags;
     113       11600 :         if (flags & GIMBAL_DEVICE_FLAGS_RETRACT) {
     114           0 :             _target.valid = false;
     115           0 :             _roi.valid = false;
     116           0 :             break;
     117             :         }
     118       11600 :         const bool yaw_ef = (flags & GIMBAL_DEVICE_FLAGS_YAW_LOCK) != 0;
     119       11600 :         const bool rates_valid = !isnan(cmd.angular_velocity_x);
     120       11600 :         const bool q_valid     = !isnan(cmd.q[0]);
     121       11600 :         if (rates_valid || q_valid) {
     122             :             // any explicit attitude/rate command cancels ROI tracking
     123       11600 :             _roi.valid = false;
     124             :         }
     125       11600 :         if (rates_valid) {
     126           0 :             _target.valid      = true;
     127           0 :             _target.is_rate    = true;
     128           0 :             _target.yaw_is_ef  = yaw_ef;
     129           0 :             _target.rates_rads = Vector3f(cmd.angular_velocity_x,
     130             :                                           cmd.angular_velocity_y,
     131             :                                           cmd.angular_velocity_z);
     132       11600 :         } else if (q_valid) {
     133       11600 :             _target.valid     = true;
     134       11600 :             _target.is_rate   = false;
     135       11600 :             _target.yaw_is_ef = yaw_ef;
     136       11600 :             _target.attitude  = Quaternion(cmd.q[0], cmd.q[1], cmd.q[2], cmd.q[3]);
     137             :         }
     138       11600 :         break;
     139             :     }
     140       13925 :     default:
     141       13925 :         break;
     142             :     }
     143       27617 : }
     144             : 
     145         277 : void MAVLinkGimbalv2::send_heartbeat()
     146             : {
     147         277 :     if (!_seen_autopilot_heartbeat) {
     148           5 :         return;
     149             :     }
     150             : 
     151         272 :     mavlink_heartbeat_t hb {};
     152         272 :     hb.type            = MAV_TYPE_GIMBAL;
     153         272 :     hb.autopilot       = MAV_AUTOPILOT_INVALID;
     154         272 :     hb.base_mode       = 0;
     155         272 :     hb.system_status   = MAV_STATE_ACTIVE;
     156         272 :     hb.mavlink_version = 3;
     157             : 
     158             :     mavlink_message_t msg;
     159         272 :     mavlink_msg_heartbeat_encode_status(
     160         272 :         _vehicle_system_id, _compid,
     161             :         &mav.status, &msg, &hb);
     162         272 :     send_mavlink_message(msg);
     163             : }
     164             : 
     165           4 : void MAVLinkGimbalv2::send_gimbal_device_information()
     166             : {
     167           4 :     mavlink_gimbal_device_information_t info {};
     168           4 :     info.time_boot_ms     = AP_HAL::millis();
     169           4 :     info.firmware_version = get_firmware_version();
     170           4 :     info.hardware_version = 0;
     171           4 :     info.roll_min  = NAN;
     172           4 :     info.roll_max  = NAN;
     173           4 :     info.pitch_min = get_pitch_min_rad();
     174           4 :     info.pitch_max = get_pitch_max_rad();
     175           4 :     info.yaw_min   = get_yaw_min_rad();
     176           4 :     info.yaw_max   = get_yaw_max_rad();
     177           4 :     info.cap_flags = get_cap_flags();
     178           4 :     strncpy_noterm(info.vendor_name, get_vendor_name(), sizeof(info.vendor_name));
     179           4 :     strncpy_noterm(info.model_name,  get_model_name(),  sizeof(info.model_name));
     180             : 
     181             :     mavlink_message_t msg;
     182           4 :     mavlink_msg_gimbal_device_information_encode_status(
     183           4 :         _vehicle_system_id, _compid,
     184             :         &mav.status, &msg, &info);
     185           4 :     send_mavlink_message(msg);
     186           4 : }
     187             : 
     188             : // update gimbal physics and compute demanded rates from the current target
     189      332820 : void MAVLinkGimbalv2::update_gimbal(const Aircraft &aircraft)
     190             : {
     191      332820 :     _vehicle_dcm = aircraft.get_dcm();
     192             : 
     193      332820 :     Matrix3f gimbal_dcm;
     194      332820 :     _gimbal.get_dcm(gimbal_dcm);
     195             : 
     196             :     // if tracking an ROI, continuously recompute the earth-frame target
     197             :     // (replicates AP_Mount_Backend::get_angle_target_to_location())
     198      332820 :     if (_roi.valid) {
     199           0 :         const Location &veh = aircraft.get_location();
     200           0 :         const float gps_x = Location::diff_longitude(_roi.loc.lng, veh.lng)
     201           0 :             * cosf(radians((veh.lat + _roi.loc.lat) * 0.00000005f)) * 0.01113195f;
     202           0 :         const float gps_y = (_roi.loc.lat - veh.lat) * 0.01113195f;
     203           0 :         int32_t target_alt_cm = 0, veh_alt_cm = 0;
     204           0 :         if (_roi.loc.get_alt_cm(Location::AltFrame::ABOVE_HOME, target_alt_cm) &&
     205           0 :             veh.get_alt_cm(Location::AltFrame::ABOVE_HOME, veh_alt_cm)) {
     206           0 :             const float gps_z = (float)(target_alt_cm - veh_alt_cm);  // cm
     207           0 :             const float horiz_cm = 100.0f * norm(gps_x, gps_y);
     208           0 :             float pitch_ef = atan2f(gps_z, horiz_cm);
     209           0 :             pitch_ef = constrain_float(pitch_ef, get_pitch_min_rad(), get_pitch_max_rad());
     210           0 :             const float yaw_ef = atan2f(gps_x, gps_y);
     211           0 :             _target.valid = true;
     212           0 :             _target.is_rate = false;
     213           0 :             _target.yaw_is_ef = true;
     214           0 :             _target.attitude.from_euler(0.0f, pitch_ef, yaw_ef);
     215             :         }
     216             :     }
     217             : 
     218      332820 :     if (_target.valid) {
     219      278506 :         Vector3f demanded_rates;
     220      278506 :         if (_target.is_rate) {
     221           0 :             demanded_rates = _target.rates_rads;
     222           0 :             if (_target.yaw_is_ef) {
     223             :                 // transform earth-frame rates to gimbal body frame
     224           0 :                 demanded_rates = gimbal_dcm.transposed() * demanded_rates;
     225             :             }
     226             :             // add vehicle body rates so that rate=0 tracks the vehicle body
     227             :             // (body-relative stabilisation); matches neutral-mode behaviour
     228           0 :             demanded_rates += gimbal_dcm.transposed() * _vehicle_dcm * aircraft.get_gyro();
     229           0 :             _gimbal.set_demanded_rates(demanded_rates);
     230      278506 :         } else if (_target.yaw_is_ef) {
     231             :             // earth-frame angle control: P-controller
     232             :             // clamp target pitch to hardware joint limits before computing error
     233             :             float t_r, t_p, t_y;
     234           0 :             _target.attitude.to_euler(t_r, t_p, t_y);
     235           0 :             t_p = constrain_float(t_p, get_pitch_min_rad(), get_pitch_max_rad());
     236           0 :             Quaternion target_clamped;
     237           0 :             target_clamped.from_euler(t_r, t_p, t_y);
     238           0 :             Quaternion q_current;
     239           0 :             q_current.from_rotation_matrix(gimbal_dcm);
     240           0 :             Quaternion q_error = q_current.inverse() * target_clamped;
     241           0 :             q_error.normalize();
     242           0 :             Vector3f av(q_error.q2, q_error.q3, q_error.q4);
     243           0 :             if (q_error.q1 < 0.0f) {
     244           0 :                 av = -av;
     245             :             }
     246           0 :             const float attitude_gain = 2.0f;  // rad/s per radian of error
     247           0 :             demanded_rates = av * (2.0f * attitude_gain);
     248           0 :             _gimbal.set_demanded_rates(demanded_rates);
     249             :         } else {
     250             :             // body-frame angle control: snap gimbal DCM to the commanded
     251             :             // body-relative orientation directly
     252      278506 :             Matrix3f body_dcm;
     253      278506 :             _target.attitude.rotation_matrix(body_dcm);
     254      278506 :             _gimbal.set_dcm(_vehicle_dcm * body_dcm);
     255      278506 :             _gimbal.set_demanded_rates(Vector3f{});
     256             :         }
     257             :     } else {
     258             :         // neutral: track vehicle body using vehicle angular rate feedforward
     259       54314 :         const Vector3f vehicle_rate_gimbal = gimbal_dcm.transposed() * _vehicle_dcm * aircraft.get_gyro();
     260       54314 :         _gimbal.set_demanded_rates(vehicle_rate_gimbal);
     261             :     }
     262      332820 :     _gimbal.update(aircraft);
     263      332820 : }
     264             : 
     265        2770 : void MAVLinkGimbalv2::send_attitude_status()
     266             : {
     267        2770 :     if (!_seen_autopilot_heartbeat) {
     268          50 :         return;
     269             :     }
     270             : 
     271             :     // report actual gimbal attitude from physics model
     272        2720 :     Matrix3f gimbal_dcm;
     273        2720 :     _gimbal.get_dcm(gimbal_dcm);
     274             : 
     275             :     uint16_t flags;
     276        2720 :     Quaternion q;
     277        2720 :     if (_target.valid && _target.yaw_is_ef) {
     278             :         // earth-frame target: report actual gimbal DCM so convergence is visible
     279           0 :         flags = GIMBAL_DEVICE_FLAGS_ROLL_LOCK |
     280             :                 GIMBAL_DEVICE_FLAGS_PITCH_LOCK |
     281             :                 GIMBAL_DEVICE_FLAGS_YAW_LOCK;
     282           0 :         q.from_rotation_matrix(gimbal_dcm);
     283        2720 :     } else if (_target.valid && !_target.yaw_is_ef && !_target.is_rate) {
     284             :         // body-frame angle target: report the commanded body-relative attitude
     285             :         // directly, matching how servo mounts report commanded angles
     286        2320 :         flags = 0;
     287        2320 :         q = _target.attitude;
     288             :     } else {
     289             :         // neutral or rate control: report body-relative attitude from DCM
     290         400 :         flags = 0;
     291         400 :         const Matrix3f body_to_gimbal = _vehicle_dcm.transposed() * gimbal_dcm;
     292         400 :         q.from_rotation_matrix(body_to_gimbal);
     293             :     }
     294             : 
     295        2720 :     mavlink_gimbal_device_attitude_status_t status {};
     296        2720 :     status.target_system    = _vehicle_system_id;
     297        2720 :     status.target_component = _vehicle_component_id;
     298        2720 :     status.time_boot_ms     = AP_HAL::millis();
     299        2720 :     status.flags = flags;
     300        2720 :     status.q[0] = q.q1;  // w
     301        2720 :     status.q[1] = q.q2;  // x
     302        2720 :     status.q[2] = q.q3;  // y
     303        2720 :     status.q[3] = q.q4;  // z
     304        2720 :     status.angular_velocity_x = 0.0f;
     305        2720 :     status.angular_velocity_y = 0.0f;
     306        2720 :     status.angular_velocity_z = 0.0f;
     307        2720 :     status.failure_flags = 0;
     308             : 
     309             :     mavlink_message_t msg;
     310        2720 :     mavlink_msg_gimbal_device_attitude_status_encode_status(
     311        2720 :         _vehicle_system_id, _compid,
     312             :         &mav.status, &msg, &status);
     313        2720 :     send_mavlink_message(msg);
     314             : }
     315             : 
     316           4 : void MAVLinkGimbalv2::send_command_ack(uint8_t target_sysid, uint8_t target_compid,
     317             :                                         MAV_CMD command, MAV_RESULT result)
     318             : {
     319           4 :     mavlink_command_ack_t ack {};
     320           4 :     ack.command          = (uint16_t)command;
     321           4 :     ack.result           = (uint8_t)result;
     322           4 :     ack.progress         = 255;
     323           4 :     ack.result_param2    = 0;
     324           4 :     ack.target_system    = target_sysid;
     325           4 :     ack.target_component = target_compid;
     326             : 
     327             :     mavlink_message_t msg;
     328           4 :     mavlink_msg_command_ack_encode_status(
     329           4 :         _vehicle_system_id, _compid,
     330             :         &mav.status, &msg, &ack);
     331           4 :     send_mavlink_message(msg);
     332           4 : }
     333             : 
     334      332820 : void MAVLinkGimbalv2::update(const Aircraft &aircraft)
     335             : {
     336      332820 :     if (!init_sitl_pointer()) {
     337           0 :         return;
     338             :     }
     339             : 
     340      332820 :     update_input();
     341      332820 :     update_gimbal(aircraft);
     342             : 
     343      332820 :     const uint32_t now_ms = AP_HAL::millis();
     344             : 
     345      332820 :     if (now_ms - _last_heartbeat_ms >= 1000) {
     346         277 :         _last_heartbeat_ms = now_ms;
     347         277 :         send_heartbeat();
     348             :     }
     349             : 
     350      332820 :     if (now_ms - _last_attitude_status_ms >= 100) {
     351        2770 :         _last_attitude_status_ms = now_ms;
     352        2770 :         send_attitude_status();
     353             :     }
     354             : }
     355             : 
     356             : }  // namespace SITL
     357             : 
     358             : #endif  // AP_SIM_MAVLINKGIMBALV2_ENABLED

Generated by: LCOV version 1.14