LCOV - code coverage report
Current view: top level - AP_WindVane - AP_WindVane.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 85 153 55.6 %
Date: 2026-09-09 11:02:08 Functions: 7 13 53.8 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : 
      16             : #include "AP_WindVane_config.h"
      17             : 
      18             : #if AP_WINDVANE_ENABLED
      19             : 
      20             : #include "AP_WindVane.h"
      21             : 
      22             : #include "AP_WindVane_Home.h"
      23             : #include "AP_WindVane_Analog.h"
      24             : #include "AP_WindVane_ModernDevice.h"
      25             : #include "AP_WindVane_Airspeed.h"
      26             : #include "AP_WindVane_RPM.h"
      27             : #include "AP_WindVane_SITL.h"
      28             : #include "AP_WindVane_NMEA.h"
      29             : 
      30             : #include <GCS_MAVLink/GCS.h>
      31             : #include <AP_AHRS/AP_AHRS.h>
      32             : #include <AP_HAL/AP_HAL.h>
      33             : #include <AP_Logger/AP_Logger.h>
      34             : #include <AP_SerialManager/AP_SerialManager.h>
      35             : 
      36             : extern const AP_HAL::HAL& hal;
      37             : 
      38             : const AP_Param::GroupInfo AP_WindVane::var_info[] = {
      39             : 
      40             :     // @Param: TYPE
      41             :     // @DisplayName: Wind Vane Type
      42             :     // @Description: Wind Vane type
      43             :     // @Values: 0:None,1:Heading when armed,2:RC input offset heading when armed,3:Analog,4:NMEA,10:SITL true,11:SITL apparent
      44             :     // @User: Standard
      45             :     // @RebootRequired: True
      46             :     AP_GROUPINFO_FLAGS("TYPE", 1, AP_WindVane, _direction_type, 0, AP_PARAM_FLAG_ENABLE),
      47             : 
      48             :     // 2 was RC_IN_NO
      49             : 
      50             :     // @Param: DIR_PIN
      51             :     // @DisplayName: Wind vane analog voltage pin for direction
      52             :     // @Description: Analog input pin to read as wind vane direction
      53             :     // @Values: 11:Pixracer,13:Pixhawk ADC4,14:Pixhawk ADC3,15:Pixhawk ADC6/Pixhawk2 ADC,50:AUX1,51:AUX2,52:AUX3,53:AUX4,54:AUX5,55:AUX6,103:Pixhawk SBUS
      54             :     // @Range: -1 127
      55             :     // @User: Standard
      56             :     AP_GROUPINFO("DIR_PIN", 3, AP_WindVane, _dir_analog_pin, WINDVANE_DEFAULT_PIN),
      57             : 
      58             :     // @Param: DIR_V_MIN
      59             :     // @DisplayName: Wind vane voltage minimum
      60             :     // @Description: Minimum voltage supplied by analog wind vane. When using pin 103, the maximum value of the parameter is 3.3V.
      61             :     // @Units: V
      62             :     // @Increment: 0.01
      63             :     // @Range: 0 5.0
      64             :     // @User: Standard
      65             :     AP_GROUPINFO("DIR_V_MIN", 4, AP_WindVane, _dir_analog_volt_min, 0.0f),
      66             : 
      67             :     // @Param: DIR_V_MAX
      68             :     // @DisplayName: Wind vane voltage maximum
      69             :     // @Description: Maximum voltage supplied by analog wind vane. When using pin 103, the maximum value of the parameter is 3.3V.
      70             :     // @Units: V
      71             :     // @Increment: 0.01
      72             :     // @Range: 0 5.0
      73             :     // @User: Standard
      74             :     AP_GROUPINFO("DIR_V_MAX", 5, AP_WindVane, _dir_analog_volt_max, 3.3f),
      75             : 
      76             :     // @Param: DIR_OFS
      77             :     // @DisplayName: Wind vane headwind offset
      78             :     // @Description: Angle offset when analog windvane is indicating a headwind, ie 0 degress relative to vehicle
      79             :     // @Units: deg
      80             :     // @Increment: 1
      81             :     // @Range: 0 360
      82             :     // @User: Standard
      83             :     AP_GROUPINFO("DIR_OFS", 6, AP_WindVane, _dir_analog_bearing_offset, 0.0f),
      84             : 
      85             :     // @Param: DIR_FILT
      86             :     // @DisplayName: apparent Wind vane direction low pass filter frequency
      87             :     // @Description: apparent Wind vane direction low pass filter frequency, a value of -1 disables filter
      88             :     // @Units: Hz
      89             :     // @User: Standard
      90             :     AP_GROUPINFO("DIR_FILT", 7, AP_WindVane, _dir_filt_hz, 0.5f),
      91             : 
      92             :     // @Param: CAL
      93             :     // @DisplayName: Wind vane calibration start
      94             :     // @Description: Start wind vane calibration by setting this to 1 or 2
      95             :     // @Values: 0:None, 1:Calibrate direction, 2:Calibrate speed
      96             :     // @User: Standard
      97             :     AP_GROUPINFO("CAL", 8, AP_WindVane, _calibration, 0),
      98             : 
      99             :     // @Param: DIR_DZ
     100             :     // @DisplayName: Wind vane deadzone when using analog sensor
     101             :     // @Description: Wind vane deadzone when using analog sensor
     102             :     // @Units: deg
     103             :     // @Increment: 1
     104             :     // @Range: 0 360
     105             :     // @User: Standard
     106             :     AP_GROUPINFO("DIR_DZ", 9, AP_WindVane, _dir_analog_deadzone, 0),
     107             : 
     108             :     // @Param: SPEED_MIN
     109             :     // @DisplayName: Wind vane cut off wind speed
     110             :     // @Description: Wind vane direction will be ignored when apparent wind speeds are below this value (if wind speed sensor is present).  If the apparent wind is consistently below this value the vane will not work
     111             :     // @Units: m/s
     112             :     // @Increment: 0.1
     113             :     // @Range: 0 5
     114             :     // @User: Standard
     115             :     AP_GROUPINFO("SPEED_MIN", 10, AP_WindVane, _dir_speed_cutoff, 0),
     116             : 
     117             :     // @Param: SPEED_TYPE
     118             :     // @DisplayName: Wind speed sensor Type
     119             :     // @Description: Wind speed sensor type
     120             :     // @Values: 0:None,1:Airspeed library,2:Modern Devices Wind Sensor,3:RPM library,4:NMEA,10:SITL true,11:SITL apparent
     121             :     // @User: Standard
     122             :     // @RebootRequired: True
     123             :     AP_GROUPINFO("SPEED_TYPE", 11, AP_WindVane, _speed_sensor_type,  0),
     124             : 
     125             :     // @Param: SPEED_PIN
     126             :     // @DisplayName: Wind vane speed sensor analog pin
     127             :     // @Description: Wind speed analog speed input pin for Modern Devices Wind Sensor rev. p
     128             :     // @Values: 11:Pixracer,13:Pixhawk ADC4,14:Pixhawk ADC3,15:Pixhawk ADC6/Pixhawk2 ADC,50:AUX1,51:AUX2,52:AUX3,53:AUX4,54:AUX5,55:AUX6,103:Pixhawk SBUS
     129             :     // @Range: -1 127
     130             :     // @User: Standard
     131             :     AP_GROUPINFO("SPEED_PIN", 12, AP_WindVane, _speed_sensor_speed_pin,  WINDSPEED_DEFAULT_SPEED_PIN),
     132             : 
     133             :     // @Param: TEMP_PIN
     134             :     // @DisplayName: Wind vane speed sensor analog temp pin
     135             :     // @Description: Wind speed sensor analog temp input pin for Modern Devices Wind Sensor rev. p, set to -1 to diasble temp readings
     136             :     // @Values: 11:Pixracer,13:Pixhawk ADC4,14:Pixhawk ADC3,15:Pixhawk ADC6/Pixhawk2 ADC,50:AUX1,51:AUX2,52:AUX3,53:AUX4,54:AUX5,55:AUX6,103:Pixhawk SBUS
     137             :     // @Range: -1 127
     138             :     // @User: Standard
     139             :     AP_GROUPINFO("TEMP_PIN", 13, AP_WindVane, _speed_sensor_temp_pin,  WINDSPEED_DEFAULT_TEMP_PIN),
     140             : 
     141             :     // @Param: SPEED_OFS
     142             :     // @DisplayName: Wind speed sensor analog voltage offset
     143             :     // @Description: Wind sensor analog voltage offset at zero wind speed
     144             :     // @Units: V
     145             :     // @Increment: 0.01
     146             :     // @Range: 0 3.3
     147             :     // @User: Standard
     148             :     AP_GROUPINFO("SPEED_OFS", 14, AP_WindVane, _speed_sensor_voltage_offset, WINDSPEED_DEFAULT_VOLT_OFFSET),
     149             : 
     150             :     // @Param: SPEED_FILT
     151             :     // @DisplayName: apparent wind speed low pass filter frequency
     152             :     // @Description: apparent Wind speed low pass filter frequency, a value of -1 disables filter
     153             :     // @Units: Hz
     154             :     // @User: Standard
     155             :     AP_GROUPINFO("SPEED_FILT", 15, AP_WindVane, _speed_filt_hz, 0.5f),
     156             : 
     157             :     // @Param: TRUE_FILT
     158             :     // @DisplayName: True speed and direction low pass filter frequency
     159             :     // @Description: True speed and direction low pass filter frequency, a value of -1 disables filter
     160             :     // @Units: Hz
     161             :     // @User: Standard
     162             :     AP_GROUPINFO("TRUE_FILT", 16, AP_WindVane, _true_filt_hz, 0.05f),
     163             : 
     164             :     AP_GROUPEND
     165             : };
     166             : 
     167             : // constructor
     168         285 : AP_WindVane::AP_WindVane()
     169             : {
     170         285 :     AP_Param::setup_object_defaults(this, var_info);
     171             : #if CONFIG_HAL_BOARD == HAL_BOARD_SITL
     172         285 :     if (_singleton) {
     173           0 :         AP_HAL::panic("Too many Wind Vane sensors");
     174             :     }
     175             : #endif
     176         285 :     _singleton = this;
     177         285 : }
     178             : 
     179             : /*
     180             :  * Get the AP_WindVane singleton
     181             :  */
     182           0 : AP_WindVane *AP_WindVane::get_singleton()
     183             : {
     184           0 :     return _singleton;
     185             : }
     186             : 
     187             : // return true if wind vane is enabled
     188      461508 : bool AP_WindVane::enabled() const
     189             : {
     190      461508 :     return _direction_type != WINDVANE_NONE;
     191             : }
     192             : 
     193             : // return true if wind speed is enabled
     194           0 : bool AP_WindVane::wind_speed_enabled() const
     195             : {
     196           0 :     return (_speed_sensor_type != WINDSPEED_NONE);
     197             : }
     198             : 
     199             : // Initialize the Wind Vane object and prepare it for use
     200         285 : void AP_WindVane::init(const AP_SerialManager& serial_manager)
     201             : {
     202             :     // don't construct twice
     203         285 :     if (_direction_driver != nullptr || _speed_driver != nullptr ) {
     204           0 :         return;
     205             :     }
     206             : 
     207             :     // wind direction
     208         285 :     switch (_direction_type) {
     209         281 :         case WindVaneType::WINDVANE_NONE:
     210             :             // WindVane disabled
     211         281 :             return;
     212             : #if AP_WINDVANE_HOME_ENABLED
     213           0 :         case WindVaneType::WINDVANE_HOME_HEADING:
     214             :         case WindVaneType::WINDVANE_PWM_PIN:
     215           0 :             _direction_driver = NEW_NOTHROW AP_WindVane_Home(*this);
     216           0 :             break;
     217             : #endif
     218             : #if AP_WINDVANE_ANALOG_ENABLED
     219           0 :         case WindVaneType::WINDVANE_ANALOG_PIN:
     220           0 :             _direction_driver = NEW_NOTHROW AP_WindVane_Analog(*this);
     221           0 :             break;
     222             : #endif
     223             : #if AP_WINDVANE_SIM_ENABLED
     224           4 :         case WindVaneType::WINDVANE_SITL_TRUE:
     225             :         case WindVaneType::WINDVANE_SITL_APPARENT:
     226           4 :             _direction_driver = NEW_NOTHROW AP_WindVane_SITL(*this);
     227           4 :             break;
     228             : #endif
     229             : #if AP_WINDVANE_NMEA_ENABLED
     230           0 :         case WindVaneType::WINDVANE_NMEA:
     231           0 :             _direction_driver = NEW_NOTHROW AP_WindVane_NMEA(*this);
     232           0 :             _direction_driver->init(serial_manager);
     233           0 :             break;
     234             : #endif
     235             :     }
     236             : 
     237             :     // wind speed
     238           4 :     switch (_speed_sensor_type) {
     239           0 :         case Speed_type::WINDSPEED_NONE:
     240           0 :             break;
     241             : #if AP_WINDVANE_AIRSPEED_ENABLED
     242           0 :         case Speed_type::WINDSPEED_AIRSPEED:
     243           0 :             _speed_driver = NEW_NOTHROW AP_WindVane_Airspeed(*this);
     244           0 :             break;
     245             : #endif
     246             : #if AP_WINDVANE_MODERNDEVICE_ENABLED
     247           0 :         case Speed_type::WINDVANE_WIND_SENSOR_REV_P:
     248           0 :             _speed_driver = NEW_NOTHROW AP_WindVane_ModernDevice(*this);
     249           0 :             break;
     250             : #endif
     251             : #if AP_WINDVANE_SIM_ENABLED
     252           4 :         case Speed_type::WINDSPEED_SITL_TRUE:
     253             :         case Speed_type::WINDSPEED_SITL_APPARENT:
     254             :             // single driver does both speed and direction
     255           4 :             if (_direction_type != _speed_sensor_type) {
     256           0 :                 _speed_driver = NEW_NOTHROW AP_WindVane_SITL(*this);
     257             :             } else {
     258           4 :                 _speed_driver = _direction_driver;
     259             :             }
     260           4 :             break;
     261             : #endif  // AP_WINDVANE_SIM_ENABLED
     262             : #if AP_WINDVANE_NMEA_ENABLED
     263           0 :         case Speed_type::WINDSPEED_NMEA:
     264             :             // single driver does both speed and direction
     265           0 :             if (_direction_type != WindVaneType::WINDVANE_NMEA) {
     266           0 :                 _speed_driver = NEW_NOTHROW AP_WindVane_NMEA(*this);
     267           0 :                 _speed_driver->init(serial_manager);
     268             :             } else {
     269           0 :                 _speed_driver = _direction_driver;
     270             :             }
     271           0 :             break;
     272             : #endif  // AP_WINDVANE_NMEA_ENABLED
     273             : #if AP_WINDVANE_RPM_ENABLED
     274           0 :         case Speed_type::WINDSPEED_RPM:
     275           0 :             _speed_driver = NEW_NOTHROW AP_WindVane_RPM(*this);
     276           0 :             break;
     277             : #endif
     278             :     }
     279             : }
     280             : 
     281             : // update wind vane, expected to be called at 20hz
     282      314458 : void AP_WindVane::update()
     283             : {
     284      314458 :     const bool have_speed = _speed_driver != nullptr;
     285      314458 :     const bool have_direction = _direction_driver != nullptr;
     286             : 
     287             :     // exit immediately if not enabled
     288      314458 :     if (!enabled() || (!have_speed && !have_direction)) {
     289      302173 :         return;
     290             :     }
     291             : 
     292             :     // calibrate if booted and disarmed
     293       12285 :     if (!hal.util->get_soft_armed()) {
     294        3495 :         if (_calibration == 1 && have_direction) {
     295           0 :             _direction_driver->calibrate();
     296        3495 :         } else if (_calibration == 2 && have_speed) {
     297           0 :             _speed_driver->calibrate();
     298        3495 :         } else if (_calibration != 0) {
     299           0 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "WindVane: driver not found");
     300           0 :             _calibration.set_and_save(0);
     301             :         }
     302        8790 :     } else if (_calibration != 0) {
     303           0 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "WindVane: disarm for cal");
     304           0 :         _calibration.set_and_save(0);
     305             :     }
     306             : 
     307             :     // read apparent wind speed
     308       12285 :     if (have_speed) {
     309       12285 :         _speed_driver->update_speed();
     310             :     }
     311             : 
     312             :     // read apparent wind direction
     313       12285 :     if (have_direction) {
     314       12285 :         _direction_driver->update_direction();
     315             :     }
     316             : 
     317       12285 :     if (have_speed && have_direction) {
     318       12285 :         if (_speed_apparent >= _dir_speed_cutoff) {
     319             :             // calculate true wind speed and direction from apparent wind
     320       12285 :             update_true_wind_speed_and_direction();
     321             :         } else {
     322             :             // assume true wind has not changed, calculate the apparent wind direction and speed
     323           0 :             update_apparent_wind_dir_from_true();
     324             :         }
     325             :     } else {
     326             :         // only have direction, can't do true wind calcs, set true direction to apparent + heading
     327           0 :         _direction_true_raw = wrap_PI(_direction_apparent_raw + AP::ahrs().get_yaw_rad());
     328           0 :         _speed_true_raw = 0.0f;
     329             :     }
     330             : 
     331             :     /*
     332             :         Apply filters
     333             :         https://en.wikipedia.org/wiki/Mean_of_circular_quantities
     334             :     */
     335             :     float filtered_sin;
     336             :     float filtered_cos;
     337             : 
     338             :     // apparent speed
     339       12285 :     if (is_positive(_speed_filt_hz)) {
     340       12285 :         _speed_apparent_filt.set_cutoff_frequency(_speed_filt_hz);
     341       12285 :         _speed_apparent = _speed_apparent_filt.apply(_speed_apparent_raw,0.05f);
     342             :     } else {
     343           0 :         _speed_apparent = _speed_apparent_raw;
     344             :     }
     345             : 
     346             :     // apparent direction
     347       12285 :     if (is_positive(_dir_filt_hz)) {
     348       12285 :         _direction_apparent_sin_filt.set_cutoff_frequency(_dir_filt_hz);
     349       12285 :         _direction_apparent_cos_filt.set_cutoff_frequency(_dir_filt_hz);
     350       12285 :         filtered_sin = _direction_apparent_sin_filt.apply(sinf(_direction_apparent_raw),0.05f);
     351       12285 :         filtered_cos = _direction_apparent_cos_filt.apply(cosf(_direction_apparent_raw),0.05f);
     352       12285 :         _direction_apparent = atan2f(filtered_sin, filtered_cos);
     353             :     } else {
     354           0 :         _direction_apparent = _direction_apparent_raw;
     355             :     }
     356             : 
     357             :     // apparent direction for tack threshold, hard coded freq
     358       12285 :     filtered_sin = _tack_sin_filt.apply(sinf(_direction_apparent_raw),0.05f);
     359       12285 :     filtered_cos = _tack_cos_filt.apply(cosf(_direction_apparent_raw),0.05f);
     360       12285 :     _direction_tack = atan2f(filtered_sin, filtered_cos);
     361       12285 :     _current_tack = is_negative(_direction_tack) ? Sailboat_Tack::TACK_PORT : Sailboat_Tack::TACK_STARBOARD;
     362             : 
     363             :     // true wind direction and speed, both at the same freq
     364       12285 :     if (is_positive(_true_filt_hz)) {
     365       12285 :         _speed_true_filt.set_cutoff_frequency(_true_filt_hz);
     366       12285 :         _direction_true_sin_filt.set_cutoff_frequency(_true_filt_hz);
     367       12285 :         _direction_true_cos_filt.set_cutoff_frequency(_true_filt_hz);
     368             : 
     369       12285 :         _speed_true = _speed_true_filt.apply(_speed_true_raw,0.05f);
     370       12285 :         filtered_sin = _direction_true_sin_filt.apply(sinf(_direction_true_raw),0.05f);
     371       12285 :         filtered_cos = _direction_true_cos_filt.apply(cosf(_direction_true_raw),0.05f);
     372       12285 :         _direction_true = atan2f(filtered_sin, filtered_cos);
     373             :     } else {
     374           0 :         _speed_true = _speed_true_raw;
     375           0 :         _direction_true = _direction_true_raw;
     376             :     }
     377             : 
     378             : #if HAL_LOGGING_ENABLED
     379             : // @LoggerMessage: WIND
     380             : // @Description: Windvane readings
     381             : // @Field: TimeUS: Time since system startup
     382             : // @Field: DrRaw: raw apparent wind direction direct from sensor, in body-frame
     383             : // @Field: DrApp: Apparent wind direction, in body-frame
     384             : // @Field: DrTru: True wind direction
     385             : // @Field: SpdRaw: raw wind speed direct from sensor
     386             : // @Field: SpdApp: Apparent wind Speed
     387             : // @Field: SpdTru: True wind speed
     388       24570 :     AP::logger().WriteStreaming("WIND", "TimeUS,DrRaw,DrApp,DrTru,SpdRaw,SpdApp,SpdTru",
     389             :                         "sddhnnn", "F000000", "Qffffff",
     390             :                         AP_HAL::micros64(),
     391       12285 :                         degrees(_direction_apparent_raw),
     392       12285 :                         degrees(_direction_apparent),
     393       12285 :                         degrees(_direction_true),
     394       12285 :                         _speed_apparent_raw,
     395       12285 :                         _speed_apparent,
     396       12285 :                         _speed_true);
     397             : #endif
     398             : 
     399             : }
     400             : 
     401         113 : void AP_WindVane::record_home_heading()
     402             : {
     403         113 :     _home_heading = AP::ahrs().get_yaw_rad();
     404         113 : }
     405             : 
     406             : // to start direction calibration from mavlink or other
     407           0 : bool AP_WindVane::start_direction_calibration()
     408             : {
     409           0 :     if (enabled() && (_calibration == 0)) {
     410           0 :         _calibration.set(1);
     411           0 :         return true;
     412             :     }
     413           0 :     return false;
     414             : }
     415             : 
     416             : // to start speed calibration from mavlink or other
     417           0 : bool AP_WindVane::start_speed_calibration()
     418             : {
     419           0 :     if (enabled() && (_calibration == 0)) {
     420           0 :         _calibration.set(2);
     421           0 :         return true;
     422             :     }
     423           0 :     return false;
     424             : }
     425             : 
     426             : // send mavlink wind message
     427      124899 : void AP_WindVane::send_wind(mavlink_channel_t chan) const
     428             : {
     429             :     // exit immediately if not enabled
     430      124899 :     if (!enabled()) {
     431      120045 :         return;
     432             :     }
     433             : 
     434             :     // send wind
     435        4854 :     mavlink_msg_wind_send(
     436             :         chan,
     437             :         wrap_360(degrees(get_true_wind_direction_rad())),
     438             :         get_true_wind_speed(),
     439             :         0);
     440             : 
     441             :     // send apparent wind using named floats
     442             :     // TODO: create a dedicated MAVLink message
     443        4854 :     gcs().send_named_float("AppWndSpd", get_apparent_wind_speed());
     444        4854 :     gcs().send_named_float("AppWndDir", degrees(get_apparent_wind_direction_rad()));
     445             : 
     446             : }
     447             : 
     448             : // calculate true wind speed and direction from apparent wind
     449             : // https://en.wikipedia.org/wiki/Apparent_wind
     450       12285 : void AP_WindVane::update_true_wind_speed_and_direction()
     451             : {
     452             :     // if no vehicle speed, can't do calcs
     453       12285 :     Vector3f veh_velocity;
     454       12285 :     if (!AP::ahrs().get_velocity_NED(veh_velocity)) {
     455             :         // if no vehicle speed use apparent speed and direction directly
     456         327 :         _direction_true_raw = _direction_apparent_raw;
     457         327 :         _speed_true_raw = _speed_apparent;
     458         327 :         return;
     459             :     }
     460             : 
     461             :     // convert apparent wind speed and direction to 2D vector in same frame as vehicle velocity
     462       11958 :     const float wind_dir_180 = _direction_apparent_raw + AP::ahrs().get_yaw_rad() + radians(180);
     463       11958 :     const Vector2f wind_apparent_vec(cosf(wind_dir_180) * _speed_apparent, sinf(wind_dir_180) * _speed_apparent);
     464             : 
     465             :     // add vehicle velocity
     466       11958 :     Vector2f wind_true_vec = Vector2f(wind_apparent_vec.x + veh_velocity.x, wind_apparent_vec.y + veh_velocity.y);
     467             : 
     468             :     // calculate true speed and direction
     469       11958 :     _direction_true_raw = wrap_PI(atan2f(wind_true_vec.y, wind_true_vec.x) - radians(180));
     470       11958 :     _speed_true_raw = wind_true_vec.length();
     471             : }
     472             : 
     473             : // apparent wind is low, can't trust sensors, assume true wind has not changed and calculate apparent wind
     474           0 : void AP_WindVane::update_apparent_wind_dir_from_true()
     475             : {
     476             :     // if no vehicle speed, can't do calcs
     477           0 :     Vector3f veh_velocity;
     478           0 :     if (!AP::ahrs().get_velocity_NED(veh_velocity)) {
     479           0 :         return;
     480             :     }
     481             : 
     482             :     // convert true wind speed and direction to 2D vector in same frame as vehicle velocity
     483           0 :     const float wind_dir_180 = _direction_true + radians(180);
     484           0 :     const Vector2f wind_true_vec(cosf(wind_dir_180) * _speed_true, sinf(wind_dir_180) * _speed_true);
     485             : 
     486             :     // add vehicle velocity
     487           0 :     Vector2f wind_apparent_vec = Vector2f(wind_true_vec.x - veh_velocity.x, wind_true_vec.y - veh_velocity.y);
     488             : 
     489             :     // calculate apartment speed and direction
     490           0 :     _direction_apparent_raw = wrap_PI(atan2f(wind_apparent_vec.y, wind_apparent_vec.x) - radians(180) - AP::ahrs().get_yaw_rad());
     491           0 :     _speed_apparent_raw = wind_apparent_vec.length();
     492             : }
     493             : 
     494             : AP_WindVane *AP_WindVane::_singleton = nullptr;
     495             : 
     496             : namespace AP {
     497           0 :     AP_WindVane *windvane()
     498             :     {
     499           0 :         return AP_WindVane::get_singleton();
     500             :     }
     501             : };
     502             : 
     503             : #endif  // AP_WINDVANE_ENABLED

Generated by: LCOV version 1.14