LCOV - code coverage report
Current view: top level - SITL - SIM_FlightAxis.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 378 0.0 %
Date: 2026-09-09 11:02:08 Functions: 0 14 0.0 %

          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             :   simulator connector for FlightAxis
      17             : */
      18             : 
      19             : #include "SIM_FlightAxis.h"
      20             : 
      21             : #if AP_SIM_FLIGHTAXIS_ENABLED
      22             : 
      23             : #include <arpa/inet.h>
      24             : #include <errno.h>
      25             : #include <fcntl.h>
      26             : #include <stdio.h>
      27             : #include <stdarg.h>
      28             : #include <sys/stat.h>
      29             : #include <sys/types.h>
      30             : #include <sys/time.h>
      31             : 
      32             : #include <AP_HAL/AP_HAL.h>
      33             : #include <AP_Logger/AP_Logger.h>
      34             : 
      35             : extern const AP_HAL::HAL& hal;
      36             : 
      37             : using namespace SITL;
      38             : 
      39             : const AP_Param::GroupInfo FlightAxis::var_info[] = {
      40             :     // @Param: OPTS
      41             :     // @DisplayName: FlightAxis options
      42             :     // @Description: Bitmask of FlightAxis options
      43             :     // @Bitmask: 0: Reset position on startup
      44             :     // @Bitmask: 1: Swap first 4 and last 4 servos (for quadplane testing)
      45             :     // @Bitmask: 2: Demix heli servos and send roll/pitch/collective/yaw
      46             :     // @Bitmask: 3: Don't print frame rate stats
      47             :     // @Bitmask: 4: Don't log Dt stats
      48             :     // @User: Advanced
      49             :     AP_GROUPINFO("OPTS", 1, FlightAxis, _options, uint32_t(Option::ResetPosition)),
      50             : 
      51             :     // @Param: SAMPLEHZ
      52             :     // @DisplayName: FlightAxis IMU synthetic sample rate
      53             :     // @Description: FlightAxis IMU synthetic sample rate
      54             :     // @User: Advanced
      55             :     AP_GROUPINFO("SAMPLEHZ", 2, FlightAxis, _samplehz, 2000),
      56             :     AP_GROUPEND
      57             : };
      58             : 
      59             : /*
      60             :   we use a thread for socket creation to reduce the impact of socket
      61             :   creation latency. These condition variables are used to synchronise
      62             :   the thread
      63             :  */
      64             : static HAL_BinarySemaphore sockcond1;
      65             : static HAL_BinarySemaphore sockcond2;
      66             : 
      67             : // the asprintf() calls are not worth checking for SITL
      68             : #pragma GCC diagnostic ignored "-Wunused-result"
      69             : 
      70             : static const struct {
      71             :     const char *name;
      72             :     float value;
      73             :     bool save;
      74             : } sim_defaults[] = {
      75             :     { "BRD_OPTIONS", 0},
      76             :     { "AHRS_EKF_TYPE", 10 },
      77             :     { "INS_GYR_CAL", 0 },
      78             :     { "BATT_MONITOR", 4 },
      79             :     { "RC1_MIN", 1000, true },
      80             :     { "RC1_MAX", 2000, true },
      81             :     { "RC2_MIN", 1000, true },
      82             :     { "RC2_MAX", 2000, true },
      83             :     { "RC3_MIN", 1000, true },
      84             :     { "RC3_MAX", 2000, true },
      85             :     { "RC4_MIN", 1000, true },
      86             :     { "RC4_MAX", 2000, true },
      87             :     { "RC2_REVERSED", 1 }, // interlink has reversed rc2
      88             :     { "SERVO1_MIN", 1000 },
      89             :     { "SERVO1_MAX", 2000 },
      90             :     { "SERVO2_MIN", 1000 },
      91             :     { "SERVO2_MAX", 2000 },
      92             :     { "SERVO3_MIN", 1000 },
      93             :     { "SERVO3_MAX", 2000 },
      94             :     { "SERVO4_MIN", 1000 },
      95             :     { "SERVO4_MAX", 2000 },
      96             :     { "SERVO5_MIN", 1000 },
      97             :     { "SERVO5_MAX", 2000 },
      98             :     { "SERVO6_MIN", 1000 },
      99             :     { "SERVO6_MAX", 2000 },
     100             :     { "SERVO6_MIN", 1000 },
     101             :     { "SERVO6_MAX", 2000 },
     102             :     { "INS_ACC2OFFS_X",    0.001 },
     103             :     { "INS_ACC2OFFS_Y",    0.001 },
     104             :     { "INS_ACC2OFFS_Z",    0.001 },
     105             :     { "INS_ACC2SCAL_X",    1.001 },
     106             :     { "INS_ACC2SCAL_Y",    1.001 },
     107             :     { "INS_ACC2SCAL_Z",    1.001 },
     108             :     { "INS_ACCOFFS_X",     0.001 },
     109             :     { "INS_ACCOFFS_Y",     0.001 },
     110             :     { "INS_ACCOFFS_Z",     0.001 },
     111             :     { "INS_ACCSCAL_X",     1.001 },
     112             :     { "INS_ACCSCAL_Y",     1.001 },
     113             :     { "INS_ACCSCAL_Z",     1.001 },
     114             :     { "RPM1_TYPE", 10 },
     115             : };
     116             : 
     117             : /*
     118             :   get system timestamp in seconds
     119             :  */
     120           0 : static double timestamp_sec()
     121             : {
     122             :     struct timeval tval;
     123           0 :     gettimeofday(&tval,NULL);
     124           0 :     return tval.tv_sec + (tval.tv_usec*1.0e-6);
     125             : }
     126             : 
     127             : #if defined(CYGWIN_BUILD)
     128             : #include <windows.h>
     129             : 
     130             : /*
     131             :   usleep on cygwin is broken for small times: https://stackoverflow.com/questions/6254703/thread-sleep-for-less-than-1-millisecond
     132             :   the solution is to define our own busy wait sleep
     133             :  */
     134             : static void us_wait(int64_t microseconds) {
     135             :     LARGE_INTEGER frequency;
     136             :     LARGE_INTEGER start_time, current_time;
     137             : 
     138             :     QueryPerformanceFrequency(&frequency); // Get the frequency of the performance counter
     139             :     QueryPerformanceCounter(&start_time);  // Get the current time
     140             : 
     141             :     // Calculate the number of ticks required for the specified microseconds
     142             :     int64_t ticks_to_wait = (int64_t)microseconds * frequency.QuadPart / 1000000;
     143             :     int64_t end_ticks = start_time.QuadPart + ticks_to_wait;
     144             : 
     145             :     // Busy-wait until the desired time has elapsed
     146             :     do {
     147             :         QueryPerformanceCounter(&current_time);
     148             :     } while (current_time.QuadPart < end_ticks);
     149             : }
     150             : #else
     151             : #define us_wait(x) usleep(x)
     152             : #endif
     153             : 
     154           0 : FlightAxis::FlightAxis(const char *frame_str) :
     155           0 :     Aircraft(frame_str)
     156             : {
     157           0 :     AP::sitl()->models.flightaxis_ptr = this;
     158           0 :     AP_Param::setup_object_defaults(this, var_info);
     159             : 
     160           0 :     use_time_sync = false;
     161           0 :     flightaxis_sync_imus_to_frames = true;  // tell the IMUs to advance on each frame that is processed
     162           0 :     rate_hz = 250 / target_speedup;
     163           0 :     if(strstr(frame_str, "helidemix") != nullptr) {
     164           0 :         _options.set(_options | uint32_t(Option::HeliDemix));
     165             :     }
     166           0 :     if(strstr(frame_str, "rev4") != nullptr) {
     167           0 :         _options.set(_options | uint32_t(Option::Rev4Servos));
     168             :     }
     169             : 
     170           0 :     const char *colon = strchr(frame_str, ':');
     171           0 :     if (colon) {
     172           0 :         controller_ip = colon+1;
     173             :     }
     174           0 :     for (uint8_t i=0; i<ARRAY_SIZE(sim_defaults); i++) {
     175           0 :         AP_Param::set_default_by_name(sim_defaults[i].name, sim_defaults[i].value);
     176           0 :         if (sim_defaults[i].save) {
     177             :             enum ap_var_type ptype;
     178           0 :             AP_Param *p = AP_Param::find(sim_defaults[i].name, &ptype);
     179           0 :             if (!p->configured()) {
     180           0 :                 p->save();
     181             :             }
     182             :         }
     183             :     }
     184             : 
     185           0 :     if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&FlightAxis::socket_creator, void), "SocketCreator", 8192,
     186             :                                       AP_HAL::Scheduler::PRIORITY_BOOST, 0)) {
     187           0 :         printf("Failed to create socket_creator thread\n");
     188             :     }
     189           0 : }
     190             : 
     191             : /*
     192             :   extremely primitive SOAP parser that assumes the format used by FlightAxis
     193             : */
     194           0 : void FlightAxis::parse_reply(const char *reply)
     195             : {
     196           0 :     const char *reply0 = reply;
     197           0 :     for (uint16_t i=0; i<num_keys; i++) {
     198           0 :         const char *p = strstr(reply, keytable[i].key);
     199           0 :         if (p == nullptr) {
     200           0 :             p = strstr(reply0, keytable[i].key);
     201             :         }
     202           0 :         if (p == nullptr) {
     203           0 :             printf("Failed to find key %s\n", keytable[i].key);
     204           0 :             controller_started = false;
     205           0 :             break;
     206             :         }
     207           0 :         p += strlen(keytable[i].key) + 1;
     208             :         double v;
     209           0 :         if (strncmp(p, "true", 4) == 0) {
     210           0 :             v = 1;
     211           0 :         } else if (strncmp(p, "false", 5) == 0) {
     212           0 :             v = 0;
     213             :         } else {
     214           0 :             v = atof(p);
     215             :         }
     216           0 :         keytable[i].ref = v;
     217             :         // this assumes key order and allows us to decode arrays
     218           0 :         p = strchr(p, '>');
     219           0 :         if (p != nullptr) {
     220           0 :             reply = p;
     221             :         }
     222             :     }
     223           0 : }
     224             : 
     225             : 
     226             : /*
     227             :   make a SOAP request, returning body of reply
     228             :  */
     229           0 : bool FlightAxis::soap_request_start(const char *action, const char *fmt, ...)
     230             : {
     231             :     va_list ap;
     232             :     char *req1;
     233             : 
     234           0 :     if (sock) {
     235           0 :         delete sock;
     236           0 :         sock = nullptr;
     237             :     }
     238             : 
     239           0 :     va_start(ap, fmt);
     240           0 :     vasprintf(&req1, fmt, ap);
     241           0 :     va_end(ap);
     242             : 
     243             :     // consumer/producer pattern
     244           0 :     while (sock == nullptr) {
     245           0 :         sock_outsem.wait_blocking();
     246           0 :         sock = socknext;
     247           0 :         socknext = nullptr;
     248           0 :         sock_insem.signal();
     249             :     }
     250             : 
     251             :     char *req;
     252           0 :     asprintf(&req, R"(POST / HTTP/1.1
     253             : soapaction: '%s'
     254             : content-length: %u
     255             : content-type: text/xml;charset='UTF-8'
     256             : Connection: Keep-Alive
     257             : 
     258             : %s)",
     259             :              action,
     260           0 :              (unsigned)strlen(req1), req1);
     261           0 :     sock->send(req, strlen(req));
     262           0 :     free(req1);
     263           0 :     free(req);
     264           0 :     return true;
     265             : }
     266             : 
     267           0 : char *FlightAxis::soap_request_end(uint32_t timeout_ms)
     268             : {
     269           0 :     if (!sock) {
     270           0 :         return nullptr;
     271             :     }
     272           0 :     if (!sock->pollin(timeout_ms)) {
     273           0 :         return nullptr;
     274             :     }
     275           0 :     sock->set_blocking(true);
     276           0 :     ssize_t ret = sock->recv(replybuf, sizeof(replybuf)-1, 1000);
     277           0 :     if (ret <= 0) {
     278           0 :         return nullptr;
     279             :     }
     280           0 :     replybuf[ret] = 0;
     281             : 
     282           0 :     char *p = strstr(replybuf, "Content-Length: ");
     283           0 :     if (p == nullptr) {
     284           0 :         delete sock;
     285           0 :         sock = nullptr;
     286           0 :         printf("No Content-Length\n");
     287           0 :         return nullptr;
     288             :     }
     289             : 
     290             :     // get the content length
     291           0 :     uint32_t content_length = strtoul(p+16, nullptr, 10);
     292           0 :     char *body = strstr(p, "\r\n\r\n");
     293           0 :     if (body == nullptr) {
     294           0 :         printf("No body\n");
     295           0 :         delete sock;
     296           0 :         sock = nullptr;
     297           0 :         return nullptr;
     298             :     }
     299           0 :     body += 4;
     300             : 
     301             :     // get the rest of the body
     302           0 :     int32_t expected_length = content_length + (body - replybuf);
     303           0 :     if (expected_length >= (int32_t)sizeof(replybuf)) {
     304           0 :         printf("Reply too large %i\n", expected_length);
     305           0 :         delete sock;
     306           0 :         sock = nullptr;
     307           0 :         return nullptr;
     308             :     }
     309           0 :     while (ret < expected_length) {
     310           0 :         ssize_t ret2 = sock->recv(&replybuf[ret], sizeof(replybuf)-(1+ret), 1000);
     311           0 :         if (ret2 <= 0) {
     312           0 :             delete sock;
     313           0 :             sock = nullptr;
     314           0 :             return nullptr;
     315             :         }
     316             :         // nul terminate
     317           0 :         replybuf[ret+ret2] = 0;
     318           0 :         ret += ret2;
     319             :     }
     320           0 :     delete sock;
     321           0 :     sock = nullptr;
     322             : 
     323           0 :     return strdup(replybuf);
     324             : }
     325             : 
     326           0 : bool FlightAxis::exchange_data(const struct sitl_input &input)
     327             : {
     328           0 :     if (!sock &&
     329           0 :         (!controller_started ||
     330           0 :          is_zero(next_state.m_flightAxisControllerIsActive) ||
     331           0 :          !is_zero(next_state.m_resetButtonHasBeenPressed))) {
     332           0 :         start_controller();
     333             :     }
     334             : 
     335           0 :     if (!sock) {
     336           0 :         send_request_message(input);
     337             :     }
     338           0 :     if (sock) {
     339           0 :         bool ret = process_reply_message();
     340           0 :         if (ret) {
     341           0 :             us_wait(250);
     342           0 :             send_request_message(input);
     343             :         }
     344           0 :         return ret;
     345             :     }
     346           0 :     return false;
     347             : }
     348             : 
     349           0 : void FlightAxis::start_controller()
     350             : {
     351           0 :     printf("Starting controller at %s\n", controller_ip);
     352             :     // call a restore first. This allows us to connect after the aircraft is changed in RealFlight
     353           0 :     soap_request_start("RestoreOriginalControllerDevice", R"(<?xml version='1.0' encoding='UTF-8'?>
     354             : <soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
     355             : <soap:Body>
     356             : <RestoreOriginalControllerDevice><a>1</a><b>2</b></RestoreOriginalControllerDevice>
     357             : </soap:Body>
     358             : </soap:Envelope>)");
     359           0 :     free(soap_request_end(1000UL));
     360           0 :     if(option_is_set(Option::ResetPosition)) {
     361           0 :         soap_request_start("ResetAircraft", R"(<?xml version='1.0' encoding='UTF-8'?>
     362             : <soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
     363             : <soap:Body>
     364             : <ResetAircraft><a>1</a><b>2</b></ResetAircraft>
     365             : </soap:Body>
     366             : </soap:Envelope>)");
     367           0 :         free(soap_request_end(1000UL));
     368             :     }
     369           0 :     soap_request_start("InjectUAVControllerInterface", R"(<?xml version='1.0' encoding='UTF-8'?>
     370             : <soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
     371             : <soap:Body>
     372             : <InjectUAVControllerInterface><a>1</a><b>2</b></InjectUAVControllerInterface>
     373             : </soap:Body>
     374             : </soap:Envelope>)");
     375           0 :     free(soap_request_end(1000UL));
     376           0 :     activation_frame_counter = frame_counter;
     377           0 :     controller_started = true;
     378           0 : }
     379             : 
     380           0 : void FlightAxis::send_request_message(const struct sitl_input &input)
     381             : { 
     382             :     // maximum number of servos to send is 12 with new FlightAxis
     383           0 :     float scaled_servos[12] {};
     384           0 :     uint16_t valid_channels = 0;
     385           0 :     for (uint8_t i=0; i<ARRAY_SIZE(scaled_servos); i++) {
     386           0 :         if (input.servos[i] == 0) {
     387           0 :             continue;
     388             :         }
     389           0 :         scaled_servos[i] = (input.servos[i] - 1000) / 1000.0f;
     390           0 :         valid_channels |= 1U << i;
     391             :     }
     392             : 
     393           0 :     if (option_is_set(Option::Rev4Servos)) {
     394             :         // swap first 4 and second 4 servos, for quadplane testing
     395             :         float saved[4];
     396           0 :         memcpy(saved, &scaled_servos[0], sizeof(saved));
     397           0 :         memcpy(&scaled_servos[0], &scaled_servos[4], sizeof(saved));
     398           0 :         memcpy(&scaled_servos[4], saved, sizeof(saved));
     399             :         // and the validity flags:
     400           0 :         uint16_t new_channels = 0;
     401           0 :         new_channels |= (valid_channels & 0b1111111100000000);
     402           0 :         new_channels |= (valid_channels & 0b11110000) >> 4;
     403           0 :         new_channels |= (valid_channels & 0b00001111) << 4;
     404           0 :         valid_channels = new_channels;
     405             :     }
     406             : 
     407           0 :     if (option_is_set(Option::HeliDemix)) {
     408             :         // FlightAxis expects "roll/pitch/collective/yaw" input
     409           0 :         float swash1 = scaled_servos[0];
     410           0 :         float swash2 = scaled_servos[1];
     411           0 :         float swash3 = scaled_servos[2];
     412             : 
     413           0 :         float roll_rate = swash1 - swash2;
     414           0 :         float pitch_rate = ((swash1+swash2) / 2.0f - swash3);
     415           0 :         float col = (swash1 + swash2 + swash3) / 3.0;
     416             : 
     417           0 :         scaled_servos[0] = constrain_float(roll_rate + 0.5, 0, 1);
     418           0 :         scaled_servos[1] = constrain_float(pitch_rate + 0.5, 0, 1);
     419           0 :         scaled_servos[2] = constrain_float(col, 0, 1);
     420             :     }
     421             : 
     422           0 :     const uint16_t channels = hal.scheduler->is_system_initialized()?4095:0;
     423           0 :     soap_request_start("ExchangeData", R"(<?xml version='1.0' encoding='UTF-8'?><soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
     424             : <soap:Body>
     425             : <ExchangeData>
     426             : <pControlInputs>
     427             : <m-selectedChannels>%u</m-selectedChannels>
     428             : <m-channelValues-0to1>
     429             : <item>%.4f</item>
     430             : <item>%.4f</item>
     431             : <item>%.4f</item>
     432             : <item>%.4f</item>
     433             : <item>%.4f</item>
     434             : <item>%.4f</item>
     435             : <item>%.4f</item>
     436             : <item>%.4f</item>
     437             : <item>%.4f</item>
     438             : <item>%.4f</item>
     439             : <item>%.4f</item>
     440             : <item>%.4f</item>
     441             : </m-channelValues-0to1>
     442             : </pControlInputs>
     443             : </ExchangeData>
     444             : </soap:Body>
     445             : </soap:Envelope>)",
     446             :                         channels,
     447           0 :                         scaled_servos[0],
     448           0 :                         scaled_servos[1],
     449           0 :                         scaled_servos[2],
     450           0 :                         scaled_servos[3],
     451           0 :                         scaled_servos[4],
     452           0 :                         scaled_servos[5],
     453           0 :                         scaled_servos[6],
     454           0 :                         scaled_servos[7],
     455           0 :                         scaled_servos[8],
     456           0 :                         scaled_servos[9],
     457           0 :                         scaled_servos[10],
     458           0 :                         scaled_servos[11]);
     459           0 : }
     460             : 
     461           0 : bool FlightAxis::process_reply_message()
     462             : {
     463           0 :     char *reply = nullptr;
     464           0 :     reply = soap_request_end(1);
     465           0 :     if (reply == nullptr) {
     466           0 :         sock_error_count++;
     467           0 :         if (sock_error_count >= 10000 && timestamp_sec() - last_recv_sec > 1) {
     468           0 :             printf("socket timeout\n");
     469           0 :             delete sock;
     470           0 :             sock = nullptr;
     471           0 :             sock_error_count = 0;
     472           0 :             last_recv_sec = timestamp_sec();
     473             :         }
     474             :     }
     475             : 
     476           0 :     if (reply) {
     477           0 :         sock_error_count = 0;
     478           0 :         last_recv_sec = timestamp_sec();
     479           0 :         parse_reply(reply);
     480           0 :         free(reply);
     481           0 :         return true;
     482             :     }
     483           0 :     return false;
     484             : }
     485             : 
     486           0 : bool FlightAxis::wait_for_sample(const struct sitl_input &input)
     487             : {
     488           0 :     double dt_seconds = 0;
     489           0 :     const float SAMPLE_INTERVAL_S = 1.0f / _samplehz.get();
     490           0 :     const float SAMPLE_INTERVAL_MIN_S = (SAMPLE_INTERVAL_S/2);   // smallest interval before moving on to the next
     491             : 
     492           0 :     double lastt_s = state.m_currentPhysicsTime_SEC;
     493             : 
     494           0 :     if (is_zero(prev_state.m_currentPhysicsTime_SEC)) {
     495           0 :         if (exchange_data(input)) {
     496           0 :             state = prev_state = next_state;
     497           0 :             sample_interval_s = SAMPLE_INTERVAL_S;
     498             :         }
     499           0 :         return false;
     500             :     }
     501             : 
     502             :     // get a new frame if we have processed all the whole samples in the previous one
     503           0 :     if (state.m_currentPhysicsTime_SEC + SAMPLE_INTERVAL_MIN_S >= next_state.m_currentPhysicsTime_SEC) {
     504           0 :         prev_state = next_state;
     505             :         do {
     506           0 :             if (exchange_data(input)) {  // updates next_state
     507           0 :                 state = prev_state; // ensure state is at the beginning of the data range
     508             : 
     509           0 :                 if (is_zero(last_time_s)) {
     510           0 :                     last_time_s = state.m_currentPhysicsTime_SEC - SAMPLE_INTERVAL_S;
     511             :                 }
     512           0 :                 dt_seconds = next_state.m_currentPhysicsTime_SEC - last_dt_sample_s;
     513           0 :                 last_delta_time_s = next_state.m_currentPhysicsTime_SEC - last_time_s;
     514           0 :                 last_dt_sample_s = next_state.m_currentPhysicsTime_SEC;
     515             :             }
     516           0 :         } while (is_zero(dt_seconds));
     517             :         // adjust the sample interval so that the number of samples in a frame is an integer
     518             :         // this prevents very small dt's which can cause havoc with flight control
     519           0 :         sample_interval_s = dt_seconds / roundf(dt_seconds / SAMPLE_INTERVAL_S);
     520             :     }
     521             : 
     522           0 :     double new_time = MIN(next_state.m_currentPhysicsTime_SEC, state.m_currentPhysicsTime_SEC + sample_interval_s);
     523           0 :     state = interpolate_frame(next_state, prev_state, new_time);
     524             : 
     525           0 :     socket_frame_counter++;
     526             : 
     527           0 :     double dt = state.m_currentPhysicsTime_SEC - lastt_s;
     528           0 :     if (dt > 0 && dt < 0.1) {
     529           0 :         if (average_frame_time_s < 1.0e-6) {
     530           0 :             average_frame_time_s = dt;
     531             :         }
     532           0 :         average_frame_time_s = average_frame_time_s * 0.98 + dt * 0.02;
     533             :     }
     534             : #if HAL_LOGGING_ENABLED
     535           0 :     if (!(option_is_set(Option::NoDtLog))) {
     536           0 :         uint64_t time_now = uint64_t(state.m_currentPhysicsTime_SEC * 1.0e6);
     537             : // @LoggerMessage: RF
     538             : // @Description: RealFlight mode messages
     539             : // @Field: TimeUS: Time since system startup
     540             : // @Field: Dt: delta time between this frame and the previous frame
     541             : // @Field: Fps: frames-per-second implied by the current delta time
     542           0 :         AP::logger().WriteStreaming("RF", "TimeUS,Dt,Fps", "QdI", time_now, dt, uint32_t(roundf(1/dt)));
     543             :     }
     544             : #endif
     545           0 :     if (last_time_s > 0) {
     546           0 :         if (dt_seconds > 0 && dt_seconds < 0.1) {
     547           0 :             if (is_zero(average_delta_time_s)) {
     548           0 :                 average_delta_time_s = dt_seconds;
     549             :             }
     550           0 :             average_delta_time_s = average_delta_time_s * 0.98 + dt_seconds * 0.02;
     551             :         }
     552             :     }
     553             : 
     554           0 :     if (is_equal(last_time_s, state.m_currentPhysicsTime_SEC)) {
     555           0 :         AP_HAL::panic("Time did not move");
     556             :     }
     557             : 
     558           0 :     return true;
     559             : }
     560             : 
     561             : /*
     562             :   update the FlightAxis simulation by one time step
     563             :  */
     564           0 : void FlightAxis::update(const struct sitl_input &input)
     565             : {
     566           0 :     if (!wait_for_sample(input)) {
     567           0 :         return;
     568             :     }
     569             : 
     570           0 :     double dt_seconds = state.m_currentPhysicsTime_SEC - last_time_s;
     571           0 :     if (dt_seconds < 0) {
     572             :         // cope with restarting RealFlight while connected
     573           0 :         initial_time_s = time_now_us * 1.0e-6f;
     574           0 :         last_time_s = state.m_currentPhysicsTime_SEC;
     575           0 :         position_offset.zero();
     576           0 :         return;
     577             :     }
     578             :     
     579             :     // initialize timer
     580           0 :     if (initial_time_s <= 0) {
     581           0 :         time_now_us = 1;    // prevent time going backwards
     582           0 :         dt_seconds = 0.001f;
     583           0 :         initial_time_s = state.m_currentPhysicsTime_SEC - dt_seconds;
     584             :     }
     585             : 
     586             :     /*
     587             :       the quaternion convention in realflight seems to have Z negative
     588             :      */
     589           0 :     Quaternion quat(state.m_orientationQuaternion_W,
     590           0 :                     state.m_orientationQuaternion_Y,
     591           0 :                     state.m_orientationQuaternion_X,
     592           0 :                     -state.m_orientationQuaternion_Z);
     593           0 :     quat.rotation_matrix(dcm);
     594             : 
     595           0 :     gyro = Vector3f(radians(constrain_float(state.m_rollRate_DEGpSEC, -2000, 2000)),
     596           0 :                     radians(constrain_float(state.m_pitchRate_DEGpSEC, -2000, 2000)),
     597           0 :                     -radians(constrain_float(state.m_yawRate_DEGpSEC, -2000, 2000))) * target_speedup;
     598             : 
     599           0 :     velocity_ef = Vector3f(state.m_velocityWorldU_MPS,
     600           0 :                              state.m_velocityWorldV_MPS,
     601           0 :                              state.m_velocityWorldW_MPS);
     602           0 :     position = Vector3d(state.m_aircraftPositionY_MTR,
     603             :                         state.m_aircraftPositionX_MTR,
     604           0 :                         -state.m_altitudeASL_MTR - home.alt*0.01);
     605           0 :     position.xy() += origin.get_distance_NE_double(home);
     606             : 
     607           0 :     accel_body = {
     608           0 :         float(state.m_accelerationBodyAX_MPS2),
     609           0 :         float(state.m_accelerationBodyAY_MPS2),
     610           0 :         float(state.m_accelerationBodyAZ_MPS2)
     611             :     };
     612             : 
     613             :     // accel on the ground is nasty in realflight, and prevents helicopter disarm
     614           0 :     if (!is_zero(state.m_isTouchingGround)) {
     615           0 :         Vector3f accel_ef = (velocity_ef - last_velocity_ef) / dt_seconds;
     616           0 :         accel_ef.z -= GRAVITY_MSS;
     617           0 :         accel_body = dcm.transposed() * accel_ef;
     618             :     }
     619             : 
     620             :     // limit to 16G to match pixhawk
     621           0 :     float a_limit = GRAVITY_MSS*16;
     622           0 :     accel_body.x = constrain_float(accel_body.x, -a_limit, a_limit);
     623           0 :     accel_body.y = constrain_float(accel_body.y, -a_limit, a_limit);
     624           0 :     accel_body.z = constrain_float(accel_body.z, -a_limit, a_limit);
     625             : 
     626             :     // offset based on first position to account for offset in RF world
     627           0 :     if (position_offset.is_zero() || !is_zero(state.m_resetButtonHasBeenPressed)) {
     628           0 :         position_offset = position;
     629             :     }
     630           0 :     position -= position_offset;
     631             : 
     632           0 :     airspeed = state.m_airspeed_MPS;
     633             : 
     634             :     /* for pitot airspeed we need the airspeed along the X axis. We
     635             :        can't get that from m_airspeed_MPS, so instead we calculate it
     636             :        from wind vector and ground speed
     637             :      */
     638           0 :     wind_ef = Vector3f(state.m_windY_MPS,state.m_windX_MPS,state.m_windZ_MPS);
     639           0 :     Vector3f airspeed_3d_ef = velocity_ef - wind_ef;
     640           0 :     Vector3f airspeed3d = dcm.mul_transpose(airspeed_3d_ef);
     641             : 
     642           0 :     if (last_imu_rotation != ROTATION_NONE) {
     643           0 :         airspeed3d = sitl->ahrs_rotation * airspeed3d;
     644             :     }
     645           0 :     airspeed_pitot = MAX(airspeed3d.x,0);
     646             : 
     647             : #if 0
     648             :     printf("WIND: %.1f %.1f %.1f AS3D %.1f %.1f %.1f\n",
     649             :            state.m_windX_MPS,
     650             :            state.m_windY_MPS,
     651             :            state.m_windZ_MPS,
     652             :            airspeed3d.x,
     653             :            airspeed3d.y,
     654             :            airspeed3d.z);
     655             : #endif
     656             : 
     657           0 :     battery_voltage = MAX(state.m_batteryVoltage_VOLTS, 0);
     658           0 :     battery_current = MAX(state.m_batteryCurrentDraw_AMPS, 0);
     659             :     // (temperature is not part of the protocol, so it is explicitly set constant here)
     660           0 :     battery_temperature_degC = 0.0f;
     661           0 :     rpm[0] = state.m_heliMainRotorRPM;
     662           0 :     rpm[1] = state.m_propRPM;
     663           0 :     motor_mask = 3;
     664             : 
     665             :     /*
     666             :       the interlink interface supports 12 input channels
     667             :      */
     668           0 :     rcin_chan_count = 12;
     669           0 :     for (uint8_t i=0; i<rcin_chan_count; i++) {
     670           0 :         rcin[i] = state.rcin[i];
     671             :     }
     672             : 
     673           0 :     update_position();
     674           0 :     time_advance();
     675           0 :     uint64_t new_time_us = (state.m_currentPhysicsTime_SEC - initial_time_s)*1.0e6;
     676           0 :     if (new_time_us < time_now_us) {
     677             :         //printf("Time going backwards by %u\n", uint32_t(time_now_us - new_time_us));
     678           0 :         uint64_t dt_us = time_now_us - new_time_us;
     679           0 :         if (dt_us > 500000) {
     680             :             // time going backwards
     681           0 :             time_now_us = new_time_us;
     682             :         }
     683             :     } else {
     684           0 :         uint64_t dt_us = new_time_us - time_now_us;
     685           0 :         const uint64_t glitch_threshold_us = 50000;
     686           0 :         const uint64_t glitch_max_us = 2000000;
     687           0 :         if (dt_us > glitch_threshold_us && dt_us < glitch_max_us) {
     688             :             // we've had a network glitch, compensate by advancing initial time
     689           0 :             float adjustment_s = (dt_us-glitch_threshold_us)*1.0e-6;
     690           0 :             initial_time_s += adjustment_s;
     691           0 :             printf("glitch %.2fs\n", adjustment_s);
     692           0 :             dt_us = glitch_threshold_us;
     693           0 :             glitch_count++;
     694             :         }
     695           0 :         if (dt_us) {    // adjust the frame time to match the dt which will vary over time
     696           0 :             adjust_frame_time(1.0e6/dt_us);
     697             :         }
     698           0 :         time_now_us += dt_us;
     699             :     }
     700             : 
     701           0 :     last_time_s = state.m_currentPhysicsTime_SEC;
     702           0 :     last_velocity_ef = velocity_ef;
     703             : 
     704             :     // update magnetic field
     705           0 :     update_mag_field_bf();
     706             : 
     707             :     // one rangefinder
     708           0 :     if (is_positive(dcm.c.z)) {
     709           0 :         rangefinder_m[0] = state.m_altitudeAGL_MTR / dcm.c.z;
     710             :     } else {
     711           0 :         rangefinder_m[0] = nanf("");
     712             :     }
     713             : 
     714           0 :     report_FPS();
     715             : }
     716             : 
     717           0 : struct FlightAxis::state FlightAxis::interpolate_frame(struct state& new_state, struct state& old_state, double new_time)
     718             : {
     719           0 :     struct state intermediate_state = old_state;
     720           0 :     double dt = new_state.m_currentPhysicsTime_SEC - old_state.m_currentPhysicsTime_SEC;
     721           0 :     if (!is_positive(dt)) {
     722           0 :         return new_state;
     723             :     }
     724             : 
     725           0 :     double interval = new_time - old_state.m_currentPhysicsTime_SEC;
     726             : 
     727             : #define INTERPOLATE(name) (intermediate_state.name = (old_state.name + interval * (new_state.name - old_state.name) / dt))
     728           0 :     INTERPOLATE(m_airspeed_MPS);
     729           0 :     INTERPOLATE(m_altitudeAGL_MTR);
     730           0 :     INTERPOLATE(m_pitchRate_DEGpSEC);
     731           0 :     INTERPOLATE(m_rollRate_DEGpSEC);
     732           0 :     INTERPOLATE(m_yawRate_DEGpSEC);
     733           0 :     INTERPOLATE(m_aircraftPositionX_MTR);
     734           0 :     INTERPOLATE(m_aircraftPositionY_MTR);
     735           0 :     INTERPOLATE(m_velocityWorldU_MPS);
     736           0 :     INTERPOLATE(m_velocityWorldV_MPS);
     737           0 :     INTERPOLATE(m_velocityWorldW_MPS);
     738           0 :     INTERPOLATE(m_accelerationBodyAX_MPS2);
     739           0 :     INTERPOLATE(m_accelerationBodyAY_MPS2);
     740           0 :     INTERPOLATE(m_accelerationBodyAZ_MPS2);
     741           0 :     INTERPOLATE(m_windX_MPS);
     742           0 :     INTERPOLATE(m_windY_MPS);
     743           0 :     INTERPOLATE(m_windZ_MPS);
     744           0 :     INTERPOLATE(m_propRPM);
     745           0 :     INTERPOLATE(m_heliMainRotorRPM);
     746           0 :     INTERPOLATE(m_batteryVoltage_VOLTS);
     747           0 :     INTERPOLATE(m_batteryCurrentDraw_AMPS);
     748           0 :     INTERPOLATE(m_orientationQuaternion_X);
     749           0 :     INTERPOLATE(m_orientationQuaternion_Y);
     750           0 :     INTERPOLATE(m_orientationQuaternion_Z);
     751           0 :     INTERPOLATE(m_orientationQuaternion_W);
     752           0 :     intermediate_state.m_currentPhysicsTime_SEC = new_time;
     753             : 
     754           0 :     return intermediate_state;
     755             : }
     756             : 
     757             : /*
     758             :   report frame rates
     759             :  */
     760           0 : void FlightAxis::report_FPS(void)
     761             : {
     762           0 :     if (frame_counter++ % _samplehz == 0) {
     763           0 :         if (!is_zero(last_frame_count_s)) {
     764           0 :             uint64_t frames = socket_frame_counter - last_socket_frame_counter;
     765           0 :             last_socket_frame_counter = socket_frame_counter;
     766           0 :             double dt = state.m_currentPhysicsTime_SEC - last_frame_count_s;
     767           0 :             if(!option_is_set(Option::SilenceFPS)) {
     768           0 :                 printf("%.2f/%.2f FPS avg=%.2f FPS net=%.2f glitches=%u\n",
     769           0 :                     frames / dt, _samplehz / dt, 1.0/average_frame_time_s, 1.0/average_delta_time_s, unsigned(glitch_count));
     770             :             }
     771             :         } else {
     772           0 :             printf("Initial position %f %f %f\n", position.x, position.y, position.z);
     773             :         }
     774           0 :         last_frame_count_s = state.m_currentPhysicsTime_SEC;
     775             :     }
     776           0 : }
     777             : 
     778             : #include <errno.h>
     779             : 
     780           0 : void FlightAxis::socket_creator(void)
     781             : {
     782           0 :     socket_pid = getpid();
     783             :     while (true) {
     784           0 :         while (socknext != nullptr) {
     785           0 :             sock_insem.wait_blocking();
     786             :         }
     787           0 :         auto *sck = NEW_NOTHROW SocketAPM_native(false);
     788           0 :         if (sck == nullptr) {
     789           0 :             us_wait(500);
     790           0 :             continue;
     791             :         }
     792             :         /*
     793             :           don't let the connection take more than 10ms (100Hz). Longer
     794             :           than this and we are better off trying for a new socket
     795             :          */
     796           0 :         if (!sck->connect_timeout(controller_ip, controller_port, 10)) {
     797           0 :             ::printf("connect failed\n");
     798           0 :             delete sck;
     799           0 :             us_wait(500);
     800           0 :             continue;
     801             :         }
     802           0 :         sck->set_blocking(false);
     803           0 :         socknext = sck;
     804           0 :         sock_outsem.signal();
     805           0 :     }
     806             : }
     807             : 
     808             : #endif // AP_SIM_FLIGHTAXIS_ENABLED

Generated by: LCOV version 1.14