LCOV - code coverage report
Current view: top level - SITL - SIM_JSBSim.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 222 0.0 %
Date: 2026-09-09 11:02:08 Functions: 0 12 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 JSBSim
      17             : */
      18             : 
      19             : #include "SIM_config.h"
      20             : 
      21             : #if AP_SIM_JSBSIM_ENABLED
      22             : 
      23             : #include "SIM_JSBSim.h"
      24             : 
      25             : #include <arpa/inet.h>
      26             : #include <errno.h>
      27             : #include <fcntl.h>
      28             : #include <stdio.h>
      29             : #include <sys/stat.h>
      30             : #include <sys/types.h>
      31             : 
      32             : #include <AP_HAL/AP_HAL.h>
      33             : 
      34             : extern const AP_HAL::HAL& hal;
      35             : 
      36             : namespace SITL {
      37             : 
      38             : // the asprintf() calls are not worth checking for SITL
      39             : #pragma GCC diagnostic ignored "-Wunused-result"
      40             : 
      41             : #define DEBUG_JSBSIM 1
      42             : 
      43           0 : JSBSim::JSBSim(const char *frame_str) :
      44             :     Aircraft(frame_str),
      45           0 :     sock_control(false),
      46           0 :     sock_fgfdm(true),
      47           0 :     initialised(false),
      48           0 :     jsbsim_script(nullptr),
      49           0 :     jsbsim_fgout(nullptr),
      50           0 :     created_templates(false),
      51           0 :     started_jsbsim(false),
      52           0 :     opened_control_socket(false),
      53           0 :     opened_fdm_socket(false),
      54           0 :     frame(FRAME_NORMAL)
      55             : {
      56           0 :     if (strstr(frame_str, "elevon")) {
      57           0 :         frame = FRAME_ELEVON;
      58           0 :     } else if (strstr(frame_str, "vtail")) {
      59           0 :         frame = FRAME_VTAIL;
      60             :     } else {
      61           0 :         frame = FRAME_NORMAL;
      62             :     }
      63           0 :     const char *model_name = strchr(frame_str, ':');
      64           0 :     if (model_name != nullptr) {
      65           0 :         jsbsim_model = model_name + 1;
      66             :     }
      67           0 : }
      68             : 
      69             : /*
      70             :   create template files
      71             :  */
      72           0 : bool JSBSim::create_templates(void)
      73             : {
      74           0 :         control_port = 5505 + instance*10;
      75           0 :         fdm_port = 5504 + instance*10;
      76             : 
      77           0 :         printf("JSBSim backend started: instance=%u control_port=%u fdm_port=%u\n",
      78           0 :                    instance, control_port, fdm_port);
      79             :                    
      80           0 :     if (created_templates) {
      81           0 :         return true;
      82             :     }
      83             : 
      84           0 :     asprintf(&jsbsim_script, "%s/jsbsim_start_%u.xml", autotest_dir, instance);
      85           0 :     asprintf(&jsbsim_fgout,  "%s/jsbsim_fgout_%u.xml", autotest_dir, instance);
      86             : 
      87           0 :     printf("JSBSim_script: '%s'\n", jsbsim_script);
      88           0 :     printf("JSBSim_fgout: '%s'\n", jsbsim_fgout);
      89             : 
      90           0 :     FILE *f = fopen(jsbsim_script, "w");
      91           0 :     if (f == nullptr) {
      92           0 :         AP_HAL::panic("Unable to create jsbsim script %s", jsbsim_script);
      93             :     }
      94           0 :     fprintf(f,
      95             : "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
      96             : "<?xml-stylesheet type=\"text/xsl\" href=\"http://jsbsim.sf.net/JSBSimScript.xsl\"?>\n"
      97             : "<runscript xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
      98             : "    xsi:noNamespaceSchemaLocation=\"http://jsbsim.sf.net/JSBSimScript.xsd\"\n"
      99             : "    name=\"Testing %s\">\n"
     100             : "\n"
     101             : "  <description>\n"
     102             : "    test ArduPlane using %s and JSBSim\n"
     103             : "  </description>\n"
     104             : "\n"
     105             : "  <use aircraft=\"%s\" initialize=\"reset\"/>\n"
     106             : "\n"
     107             : "  <!-- we control the servos via the jsbsim console\n"
     108             : "       interface on TCP 5124 -->\n"
     109             : "  <input port=\"%u\"/>\n"
     110             : "\n"
     111             : "  <run start=\"0\" end=\"10000000\" dt=\"%.6f\">\n"
     112             : "    <property value=\"0\"> simulation/notify-time-trigger </property>\n"
     113             : "\n"
     114             : "    <event name=\"start engine\">\n"
     115             : "      <condition> simulation/sim-time-sec le 0.01 </condition>\n"
     116             : "      <set name=\"propulsion/engine[0]/set-running\" value=\"1\"/>\n"
     117             : "      <notify/>\n"
     118             : "    </event>\n"
     119             : "\n"
     120             : "    <event name=\"Trim\">\n"
     121             : "      <condition>simulation/sim-time-sec ge 0.01</condition>\n"
     122             : "      <set name=\"simulation/do_simple_trim\" value=\"2\"/>\n"
     123             : "      <notify/>\n"
     124             : "    </event>\n"
     125             : "  </run>\n"
     126             : "\n"
     127             : "</runscript>\n"
     128             : "",
     129             :             jsbsim_model,
     130             :             jsbsim_model,
     131             :             jsbsim_model,
     132           0 :             control_port,
     133           0 :             1.0/rate_hz);
     134           0 :     fclose(f);
     135             : 
     136           0 :     f = fopen(jsbsim_fgout, "w");
     137           0 :     if (f == nullptr) {
     138           0 :         AP_HAL::panic("Unable to create jsbsim fgout script %s", jsbsim_fgout);
     139             :     }
     140           0 :     fprintf(f, "<?xml version=\"1.0\"?>\n"
     141             :             "<output name=\"127.0.0.1\" type=\"FLIGHTGEAR\" port=\"%u\" protocol=\"UDP\" rate=\"%f\">\n"
     142             :             "  <time type=\"simulation\" resolution=\"1e-6\"/>\n"
     143             :             "</output>",
     144           0 :             fdm_port, rate_hz);
     145           0 :     fclose(f);
     146             : 
     147             :     char *jsbsim_reset;
     148           0 :     asprintf(&jsbsim_reset, "%s/aircraft/%s/reset.xml", autotest_dir, jsbsim_model);
     149             : 
     150           0 :     printf("JSBSim_reset: '%s'\n", jsbsim_reset);
     151             : 
     152           0 :     f = fopen(jsbsim_reset, "w");
     153           0 :     if (f == nullptr) {
     154           0 :         AP_HAL::panic("Unable to create jsbsim reset script %s", jsbsim_reset);
     155             :     }
     156             :     float r, p, y;
     157           0 :     dcm.to_euler(&r, &p, &y);
     158           0 :     fprintf(f,
     159             :             "<?xml version=\"1.0\"?>\n"
     160             :             "<initialize name=\"Start up location\">\n"
     161             :             "  <latitude unit=\"DEG\" type=\"geodetic\"> %f </latitude>\n"
     162             :             "  <longitude unit=\"DEG\"> %f </longitude>\n"
     163             :             "  <altitude unit=\"M\"> 1.3 </altitude>\n"
     164             :             "  <vt unit=\"FT/SEC\"> 0.0 </vt>\n"
     165             :             "  <gamma unit=\"DEG\"> 0.0 </gamma>\n"
     166             :             "  <phi unit=\"DEG\"> 0.0 </phi>\n"
     167             :             "  <theta unit=\"DEG\"> 13.0 </theta>\n"
     168             :             "  <psi unit=\"DEG\"> %f </psi>\n"
     169             :             "</initialize>\n",
     170           0 :             home.lat*1.0e-7,
     171           0 :             home.lng*1.0e-7,
     172           0 :             degrees(y));
     173           0 :     fclose(f);
     174             : 
     175           0 :     created_templates = true;
     176           0 :     return true;
     177             : }
     178             : 
     179             : 
     180             : /*
     181             :   start JSBSim child
     182             :  */
     183           0 : bool JSBSim::start_JSBSim(void)
     184             : {
     185           0 :     if (started_jsbsim) {
     186           0 :         return true;
     187             :     }
     188           0 :     if (!open_fdm_socket()) {
     189           0 :         return false;
     190             :     }
     191             : 
     192             :     int p[2];
     193           0 :     int devnull = open("/dev/null", O_RDWR|O_CLOEXEC);
     194           0 :     if (devnull == -1) {
     195           0 :         AP_HAL::panic("Unable to open /dev/null");
     196             :     }
     197           0 :     if (pipe(p) != 0) {
     198           0 :         AP_HAL::panic("Unable to create pipe");
     199             :     }
     200           0 :     pid_t child_pid = fork();
     201           0 :     if (child_pid == 0) {
     202             :         // in child
     203           0 :         setsid();
     204           0 :         dup2(devnull, 0);
     205           0 :         dup2(p[1], 1);
     206           0 :         close(p[0]);
     207           0 :         for (uint8_t i=3; i<100; i++) {
     208           0 :             close(i);
     209             :         }
     210             :         char *logdirective;
     211             :         char *script;
     212             :         char *nice;
     213             :         char *rate;
     214             : 
     215           0 :         asprintf(&logdirective, "--logdirectivefile=%s", jsbsim_fgout);
     216           0 :         asprintf(&script, "--script=%s", jsbsim_script);
     217           0 :         asprintf(&nice, "--nice=%.8f", 10*1e-9);
     218           0 :         asprintf(&rate, "--simulation-rate=%f", rate_hz);
     219             : 
     220           0 :         if (chdir(autotest_dir) != 0) {
     221           0 :             perror(autotest_dir);
     222           0 :             exit(1);
     223             :         }
     224             : 
     225           0 :         int ret = execlp("JSBSim",
     226             :                          "JSBSim",
     227             :                          "--suspend",
     228             :                          rate,
     229             :                          nice,
     230             :                          logdirective,
     231             :                          script,
     232             :                          nullptr);
     233           0 :         if (ret != 0) {
     234           0 :             perror("JSBSim");
     235             :         }
     236           0 :         exit(1);
     237             :     }
     238           0 :     close(p[1]);
     239           0 :     jsbsim_stdout = p[0];
     240             : 
     241             :     // read startup to be sure it is running
     242             :     char c;
     243           0 :     if (read(jsbsim_stdout, &c, 1) != 1) {
     244           0 :         AP_HAL::panic("Unable to start JSBSim");
     245             :     }
     246             : 
     247           0 :     if (!expect("JSBSim Execution beginning")) {
     248           0 :         AP_HAL::panic("Failed to start JSBSim");
     249             :     }
     250           0 :     if (!open_control_socket()) {
     251           0 :         AP_HAL::panic("Failed to open JSBSim control socket");
     252             :     }
     253             : 
     254           0 :     fcntl(jsbsim_stdout, F_SETFL, fcntl(jsbsim_stdout, F_GETFL, 0) | O_NONBLOCK);
     255             : 
     256           0 :     started_jsbsim = true;
     257           0 :     check_stdout();
     258           0 :     close(devnull);
     259           0 :     return true;
     260             : }
     261             : 
     262             : /*
     263             :   check for stdout from JSBSim
     264             :  */
     265           0 : void JSBSim::check_stdout(void) const
     266             : {
     267             :     char line[100];
     268           0 :     ssize_t ret = ::read(jsbsim_stdout, line, sizeof(line));
     269           0 :     if (ret > 0) {
     270             : #if DEBUG_JSBSIM
     271           0 :         write(1, line, ret);
     272             : #endif
     273             :     }
     274           0 : }
     275             : 
     276             : /*
     277             :   a simple function to wait for a string on jsbsim_stdout
     278             :  */
     279           0 : bool JSBSim::expect(const char *str) const
     280             : {
     281           0 :     const char *basestr = str;
     282           0 :     while (*str) {
     283             :         char c;
     284           0 :         if (read(jsbsim_stdout, &c, 1) != 1) {
     285           0 :             return false;
     286             :         }
     287           0 :         if (c == *str) {
     288           0 :             str++;
     289             :         } else {
     290           0 :             str = basestr;
     291             :         }
     292             : #if DEBUG_JSBSIM
     293           0 :         write(1, &c, 1);
     294             : #endif
     295             :     }
     296           0 :     return true;
     297             : }
     298             : 
     299             : /*
     300             :   open control socket to JSBSim
     301             :  */
     302           0 : bool JSBSim::open_control_socket(void)
     303             : {
     304           0 :     if (opened_control_socket) {
     305           0 :         return true;
     306             :     }
     307           0 :     if (!sock_control.connect("127.0.0.1", control_port)) {
     308           0 :         return false;
     309             :     }
     310           0 :     printf("Opened JSBSim control socket\n");
     311           0 :     sock_control.set_blocking(false);
     312           0 :     opened_control_socket = true;
     313             : 
     314           0 :     char startup[] =
     315             :         "info\n"
     316             :         "resume\n"
     317             :         "iterate 1\n"
     318             :         "set atmosphere/turb-type 4\n";
     319           0 :     sock_control.send(startup, strlen(startup));
     320           0 :     return true;
     321             : }
     322             : 
     323             : /*
     324             :   open fdm socket from JSBSim
     325             :  */
     326           0 : bool JSBSim::open_fdm_socket(void)
     327             : {
     328           0 :     if (opened_fdm_socket) {
     329           0 :         return true;
     330             :     }
     331           0 :     if (!sock_fgfdm.bind("127.0.0.1", fdm_port)) {
     332           0 :         check_stdout();
     333           0 :         return false;
     334             :     }
     335           0 :     sock_fgfdm.set_blocking(false);
     336           0 :     sock_fgfdm.reuseaddress();
     337           0 :     opened_fdm_socket = true;
     338           0 :     return true;
     339             : }
     340             : 
     341             : 
     342             : /*
     343             :   decode and send servos
     344             : */
     345           0 : void JSBSim::send_servos(const struct sitl_input &input)
     346             : {
     347           0 :     char *buf = nullptr;
     348           0 :     float aileron  = filtered_servo_angle(input, 0);
     349           0 :     float elevator = filtered_servo_angle(input, 1);
     350           0 :     float throttle = filtered_servo_range(input, 2);
     351           0 :     float rudder   = filtered_servo_angle(input, 3);
     352           0 :     if (frame == FRAME_ELEVON) {
     353             :         // fake an elevon plane
     354           0 :         float ch1 = aileron;
     355           0 :         float ch2 = elevator;
     356           0 :         aileron  = (ch2-ch1)/2.0f;
     357             :         // the minus does away with the need for RC2_REVERSED=-1
     358           0 :         elevator = -(ch2+ch1)/2.0f;
     359           0 :     } else if (frame == FRAME_VTAIL) {
     360             :         // fake a vtail plane
     361           0 :         float ch1 = elevator;
     362           0 :         float ch2 = rudder;
     363             :         // this matches VTAIL_OUTPUT==2
     364           0 :         elevator = (ch2-ch1)/2.0f;
     365           0 :         rudder   = (ch2+ch1)/2.0f;
     366             :     }
     367           0 :     float wind_speed_fps = input.wind.speed / FEET_TO_METERS;
     368           0 :     asprintf(&buf,
     369             :              "set fcs/aileron-cmd-norm %f\n"
     370             :              "set fcs/elevator-cmd-norm %f\n"
     371             :              "set fcs/rudder-cmd-norm %f\n"
     372             :              "set fcs/throttle-cmd-norm %f\n"
     373             :              "set atmosphere/psiw-rad %f\n"
     374             :              "set atmosphere/wind-mag-fps %f\n"
     375             :              "set atmosphere/turbulence/milspec/windspeed_at_20ft_AGL-fps %f\n"
     376             :              "set atmosphere/turbulence/milspec/severity %f\n"
     377             :              "iterate 1\n",
     378             :              aileron, elevator, rudder, throttle,
     379           0 :              radians(input.wind.direction),
     380             :              wind_speed_fps,
     381           0 :              wind_speed_fps/3,
     382           0 :              input.wind.turbulence);
     383           0 :     ssize_t buflen = strlen(buf);
     384           0 :     ssize_t sent = sock_control.send(buf, buflen);
     385           0 :     free(buf);
     386           0 :     if (sent < 0) {
     387           0 :         if (errno != EAGAIN) {
     388           0 :             fprintf(stderr, "Fatal: Failed to send on control socket: %s\n",
     389           0 :                     strerror(errno));
     390           0 :             exit(1);
     391             :         }
     392             :     }
     393           0 :     if (sent < buflen) {
     394           0 :         fprintf(stderr, "Failed to send all bytes on control socket\n");
     395             :     }
     396           0 : }
     397             : 
     398             : /* nasty hack ....
     399             :    JSBSim sends in little-endian
     400             :  */
     401           0 : void FGNetFDM::ByteSwap(void)
     402             : {
     403           0 :     uint32_t *buf = (uint32_t *)this;
     404           0 :     for (uint16_t i=0; i<sizeof(*this)/4; i++) {
     405           0 :         buf[i] = ntohl(buf[i]);
     406             :     }
     407             :     // fixup the 3 doubles
     408           0 :     buf = (uint32_t *)&longitude;
     409             :     uint32_t tmp;
     410           0 :     for (uint8_t i=0; i<3; i++) {
     411           0 :         tmp = buf[0];
     412           0 :         buf[0] = buf[1];
     413           0 :         buf[1] = tmp;
     414           0 :         buf += 2;
     415             :     }
     416           0 : }
     417             : 
     418             : /*
     419             :   receive an update from the FDM
     420             :   This is a blocking function
     421             :  */
     422           0 : void JSBSim::recv_fdm(const struct sitl_input &input)
     423             : {
     424             :     FGNetFDM fdm;
     425           0 :     check_stdout();
     426             : 
     427             :     do {
     428           0 :         while (sock_fgfdm.recv(&fdm, sizeof(fdm), 100) != sizeof(fdm)) {
     429           0 :             send_servos(input);
     430           0 :             check_stdout();
     431             :         }
     432           0 :         fdm.ByteSwap();
     433           0 :     } while (fdm.cur_time == time_now_us);
     434             : 
     435           0 :     accel_body = Vector3f(fdm.A_X_pilot, fdm.A_Y_pilot, fdm.A_Z_pilot) * FEET_TO_METERS;
     436             : 
     437             :     double p, q, r;
     438           0 :     SIM::convert_body_frame(degrees(fdm.phi), degrees(fdm.theta),
     439           0 :                              degrees(fdm.phidot), degrees(fdm.thetadot), degrees(fdm.psidot),
     440             :                              &p, &q, &r);
     441           0 :     gyro = Vector3f(p, q, r);
     442             : 
     443           0 :     velocity_ef = Vector3f(fdm.v_north, fdm.v_east, fdm.v_down) * FEET_TO_METERS;
     444           0 :     location = {
     445           0 :         int32_t(RAD_TO_DEG_DOUBLE * fdm.latitude * 1.0e7),
     446           0 :         int32_t(RAD_TO_DEG_DOUBLE * fdm.longitude * 1.0e7),
     447           0 :         int32_t(fdm.agl*100 + home.alt),
     448             :         Location::AltFrame::ABSOLUTE
     449             :     };
     450           0 :     dcm.from_euler(fdm.phi, fdm.theta, fdm.psi);
     451           0 :     airspeed = fdm.vcas * KNOTS_TO_METERS_PER_SECOND;
     452           0 :     airspeed_pitot = airspeed;
     453             : 
     454             :     // update magnetic field
     455           0 :     update_mag_field_bf();
     456             :     
     457           0 :     rpm[0] = fdm.rpm[0];
     458           0 :     rpm[1] = fdm.rpm[1];
     459             :     
     460           0 :     time_now_us = fdm.cur_time;
     461           0 : }
     462             : 
     463           0 : void JSBSim::drain_control_socket()
     464             : {
     465           0 :     const uint16_t buflen = 1024;
     466             :     char buf[buflen];
     467             :     ssize_t received;
     468             :     do {
     469           0 :         received = sock_control.recv(buf, buflen, 0);
     470           0 :     } while (received > 0);
     471           0 : }
     472             : /*
     473             :   update the JSBSim simulation by one time step
     474             :  */
     475           0 : void JSBSim::update(const struct sitl_input &input)
     476             : {
     477           0 :     while (!initialised) {
     478           0 :         if (!create_templates() ||
     479           0 :             !start_JSBSim() ||
     480           0 :             !open_control_socket() ||
     481           0 :             !open_fdm_socket()) {
     482           0 :             time_now_us = 1;
     483           0 :             return;
     484             :         }
     485           0 :         initialised = true;
     486             :     }
     487           0 :     send_servos(input);
     488           0 :     recv_fdm(input);
     489           0 :     update_battery();
     490           0 :     adjust_frame_time(rate_hz);
     491           0 :     sync_frame_time();
     492           0 :     drain_control_socket();
     493             : }
     494             : 
     495             : } // namespace SITL
     496             : 
     497             : #endif  // AP_SIM_JSBSIM_ENABLED

Generated by: LCOV version 1.14