SIM_XPlane.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  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. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. GNU General Public License for more details.
  10. You should have received a copy of the GNU General Public License
  11. along with this program. If not, see <http://www.gnu.org/licenses/>.
  12. */
  13. /*
  14. simulator connector for XPlane
  15. */
  16. #include "SIM_XPlane.h"
  17. #include <errno.h>
  18. #include <fcntl.h>
  19. #include <stdio.h>
  20. #include <stdarg.h>
  21. #include <sys/stat.h>
  22. #include <sys/types.h>
  23. #include <AP_HAL/AP_HAL.h>
  24. #include <AP_Logger/AP_Logger.h>
  25. extern const AP_HAL::HAL& hal;
  26. namespace SITL {
  27. XPlane::XPlane(const char *frame_str) :
  28. Aircraft(frame_str)
  29. {
  30. use_time_sync = false;
  31. const char *colon = strchr(frame_str, ':');
  32. if (colon) {
  33. xplane_ip = colon+1;
  34. }
  35. heli_frame = (strstr(frame_str, "-heli") != nullptr);
  36. socket_in.bind("0.0.0.0", bind_port);
  37. printf("Waiting for XPlane data on UDP port %u and sending to port %u\n",
  38. (unsigned)bind_port, (unsigned)xplane_port);
  39. // XPlane sensor data is not good enough for EKF. Use fake EKF by default
  40. AP_Param::set_default_by_name("AHRS_EKF_TYPE", 10);
  41. AP_Param::set_default_by_name("INS_GYR_CAL", 0);
  42. }
  43. /*
  44. change what data is requested from XPlane. This saves the user from
  45. having to setup the data screen correctly
  46. */
  47. void XPlane::select_data(uint64_t usel_mask, uint64_t sel_mask)
  48. {
  49. struct PACKED {
  50. uint8_t marker[5] { 'D', 'S', 'E', 'L', '0' };
  51. uint32_t data[8] {};
  52. } dsel;
  53. uint8_t count = 0;
  54. for (uint8_t i=0; i<64 && count<8; i++) {
  55. if ((((uint64_t)1)<<i) & sel_mask) {
  56. dsel.data[count++] = i;
  57. printf("i=%u\n", (unsigned)i);
  58. }
  59. }
  60. if (count != 0) {
  61. socket_out.send(&dsel, sizeof(dsel));
  62. printf("Selecting %u data types 0x%llx\n", (unsigned)count, (unsigned long long)sel_mask);
  63. }
  64. struct PACKED {
  65. uint8_t marker[5] { 'U', 'S', 'E', 'L', '0' };
  66. uint32_t data[8] {};
  67. } usel;
  68. count = 0;
  69. // only de-select an output once, so we don't fight the user
  70. usel_mask &= ~unselected_mask;
  71. unselected_mask |= usel_mask;
  72. for (uint8_t i=0; i<64 && count<8; i++) {
  73. if ((((uint64_t)1)<<i) & usel_mask) {
  74. usel.data[count++] = i;
  75. }
  76. }
  77. if (count != 0) {
  78. socket_out.send(&usel, sizeof(usel));
  79. printf("De-selecting %u data types 0x%llx\n", (unsigned)count, (unsigned long long)usel_mask);
  80. }
  81. }
  82. /*
  83. receive data from X-Plane via UDP
  84. */
  85. bool XPlane::receive_data(void)
  86. {
  87. uint8_t pkt[10000];
  88. uint8_t *p = &pkt[5];
  89. const uint8_t pkt_len = 36;
  90. uint64_t data_mask = 0;
  91. const uint64_t one = 1U;
  92. const uint64_t required_mask = (one<<Times | one<<LatLonAlt | one<<Speed | one<<PitchRollHeading |
  93. one<<LocVelDistTraveled | one<<AngularVelocities | one<<Gload |
  94. one << Joystick1 | one << ThrottleCommand | one << Trim |
  95. one << PropPitch | one << EngineRPM | one << PropRPM | one << Generator |
  96. one << Mixture);
  97. Location loc {};
  98. Vector3f pos;
  99. uint32_t wait_time_ms = 1;
  100. uint32_t now = AP_HAL::millis();
  101. // if we are about to get another frame from X-Plane then wait longer
  102. if (xplane_frame_time > wait_time_ms &&
  103. now+1 >= last_data_time_ms + xplane_frame_time) {
  104. wait_time_ms = 10;
  105. }
  106. ssize_t len = socket_in.recv(pkt, sizeof(pkt), wait_time_ms);
  107. if (len < pkt_len+5 || memcmp(pkt, "DATA", 4) != 0) {
  108. // not a data packet we understand
  109. goto failed;
  110. }
  111. len -= 5;
  112. if (!connected) {
  113. // we now know the IP X-Plane is using
  114. uint16_t port;
  115. socket_in.last_recv_address(xplane_ip, port);
  116. socket_out.connect(xplane_ip, xplane_port);
  117. connected = true;
  118. printf("Connected to %s:%u\n", xplane_ip, (unsigned)xplane_port);
  119. }
  120. while (len >= pkt_len) {
  121. const float *data = (const float *)p;
  122. uint8_t code = p[0];
  123. // keep a mask of what codes we have received
  124. if (code < 64) {
  125. data_mask |= (((uint64_t)1) << code);
  126. }
  127. switch (code) {
  128. case Times: {
  129. uint64_t tus = data[3] * 1.0e6f;
  130. if (tus + time_base_us <= time_now_us) {
  131. uint64_t tdiff = time_now_us - (tus + time_base_us);
  132. if (tdiff > 1e6f) {
  133. printf("X-Plane time reset %lu\n", (unsigned long)tdiff);
  134. }
  135. time_base_us = time_now_us - tus;
  136. }
  137. uint64_t tnew = time_base_us + tus;
  138. //uint64_t dt = tnew - time_now_us;
  139. //printf("dt %u\n", (unsigned)dt);
  140. time_now_us = tnew;
  141. break;
  142. }
  143. case LatLonAlt: {
  144. loc.lat = data[1] * 1e7;
  145. loc.lng = data[2] * 1e7;
  146. loc.alt = data[3] * FEET_TO_METERS * 100.0f;
  147. const float altitude_above_ground = data[4] * FEET_TO_METERS;
  148. ground_level = loc.alt * 0.01f - altitude_above_ground;
  149. break;
  150. }
  151. case Speed:
  152. airspeed = data[2] * KNOTS_TO_METERS_PER_SECOND;
  153. airspeed_pitot = airspeed;
  154. break;
  155. case AoA:
  156. // ignored
  157. break;
  158. case Trim:
  159. if (heli_frame) {
  160. // use flaps for collective as no direct collective data input
  161. rcin[2] = data[4];
  162. }
  163. break;
  164. case PitchRollHeading: {
  165. float roll, pitch, yaw;
  166. pitch = radians(data[1]);
  167. roll = radians(data[2]);
  168. yaw = radians(data[3]);
  169. dcm.from_euler(roll, pitch, yaw);
  170. break;
  171. }
  172. case AtmosphereWeather:
  173. // ignored
  174. break;
  175. case LocVelDistTraveled:
  176. pos.y = data[1];
  177. pos.z = -data[2];
  178. pos.x = -data[3];
  179. velocity_ef.y = data[4];
  180. velocity_ef.z = -data[5];
  181. velocity_ef.x = -data[6];
  182. break;
  183. case AngularVelocities:
  184. gyro.y = data[1];
  185. gyro.x = data[2];
  186. gyro.z = data[3];
  187. break;
  188. case Gload:
  189. accel_body.z = -data[5] * GRAVITY_MSS;
  190. accel_body.x = data[6] * GRAVITY_MSS;
  191. accel_body.y = data[7] * GRAVITY_MSS;
  192. break;
  193. case Joystick1:
  194. rcin_chan_count = 4;
  195. rcin[0] = (data[2] + 1)*0.5f;
  196. rcin[1] = (data[1] + 1)*0.5f;
  197. rcin[3] = (data[3] + 1)*0.5f;
  198. break;
  199. case ThrottleCommand: {
  200. if (!heli_frame) {
  201. /* getting joystick throttle input is very weird. The
  202. * problem is that XPlane sends the ThrottleCommand packet
  203. * both for joystick throttle input and for throttle that
  204. * we have provided over the link. So we need some way to
  205. * detect when we get our own values back. The trick used
  206. * is to add throttle_magic to the values we send, then
  207. * detect this offset in the data coming back. Very ugly,
  208. * but I can't find a better way of allowing joystick
  209. * input from XPlane10
  210. */
  211. bool has_magic = ((uint32_t)(data[1] * throttle_magic_scale) % 1000U) == (uint32_t)(throttle_magic * throttle_magic_scale);
  212. #pragma GCC diagnostic push
  213. #pragma GCC diagnostic ignored "-Wfloat-equal"
  214. if (data[1] < 0 ||
  215. data[1] == throttle_sent ||
  216. has_magic) {
  217. break;
  218. }
  219. #pragma GCC diagnostic pop
  220. rcin[2] = data[1];
  221. }
  222. break;
  223. }
  224. case PropPitch: {
  225. break;
  226. }
  227. case EngineRPM:
  228. rpm1 = data[1];
  229. break;
  230. case PropRPM:
  231. rpm2 = data[1];
  232. break;
  233. case Joystick2:
  234. break;
  235. case Generator:
  236. /*
  237. in order to get interlock switch on helis we map the
  238. "generator1 on/off" function of XPlane 10 to channel 8.
  239. */
  240. rcin_chan_count = 8;
  241. rcin[7] = data[1];
  242. break;
  243. case Mixture:
  244. // map channel 6 and 7 from Mixture3 and Mixture4 for extra channels
  245. rcin_chan_count = MAX(7, rcin_chan_count);
  246. rcin[5] = data[3];
  247. rcin[6] = data[4];
  248. break;
  249. }
  250. len -= pkt_len;
  251. p += pkt_len;
  252. }
  253. if (data_mask != required_mask) {
  254. // ask XPlane to change what data it sends
  255. uint64_t usel = data_mask & ~required_mask;
  256. uint64_t sel = required_mask & ~data_mask;
  257. usel &= ~unselected_mask;
  258. if (usel || sel) {
  259. select_data(usel, sel);
  260. goto failed;
  261. }
  262. }
  263. position = pos + position_zero;
  264. update_position();
  265. time_advance();
  266. accel_earth = dcm * accel_body;
  267. accel_earth.z += GRAVITY_MSS;
  268. // the position may slowly deviate due to float accuracy and longitude scaling
  269. if (loc.get_distance(location) > 4 || abs(loc.alt - location.alt)*0.01f > 2.0f) {
  270. printf("X-Plane home reset dist=%f alt=%.1f/%.1f\n",
  271. loc.get_distance(location), loc.alt*0.01f, location.alt*0.01f);
  272. // reset home location
  273. position_zero(-pos.x, -pos.y, -pos.z);
  274. home.lat = loc.lat;
  275. home.lng = loc.lng;
  276. home.alt = loc.alt;
  277. position.x = 0;
  278. position.y = 0;
  279. position.z = 0;
  280. update_position();
  281. time_advance();
  282. }
  283. update_mag_field_bf();
  284. if (now > last_data_time_ms && now - last_data_time_ms < 100) {
  285. xplane_frame_time = now - last_data_time_ms;
  286. }
  287. last_data_time_ms = AP_HAL::millis();
  288. report.data_count++;
  289. report.frame_count++;
  290. return true;
  291. failed:
  292. if (AP_HAL::millis() - last_data_time_ms > 200) {
  293. // don't extrapolate beyond 0.2s
  294. return false;
  295. }
  296. // advance time by 1ms
  297. frame_time_us = 1000;
  298. float delta_time = frame_time_us * 1e-6f;
  299. time_now_us += frame_time_us;
  300. extrapolate_sensors(delta_time);
  301. update_position();
  302. time_advance();
  303. update_mag_field_bf();
  304. report.frame_count++;
  305. return false;
  306. }
  307. /*
  308. send data to X-Plane via UDP
  309. */
  310. void XPlane::send_data(const struct sitl_input &input)
  311. {
  312. float aileron = (input.servos[0]-1500)/500.0f;
  313. float elevator = (input.servos[1]-1500)/500.0f;
  314. float throttle = (input.servos[2]-1000)/1000.0;
  315. float rudder = (input.servos[3]-1500)/500.0f;
  316. struct PACKED {
  317. uint8_t marker[5] { 'D', 'A', 'T', 'A', '0' };
  318. uint32_t code;
  319. float data[8];
  320. } d {};
  321. if (input.servos[0] == 0) {
  322. aileron = 0;
  323. }
  324. if (input.servos[1] == 0) {
  325. elevator = 0;
  326. }
  327. if (input.servos[2] == 0) {
  328. throttle = 0;
  329. }
  330. if (input.servos[3] == 0) {
  331. rudder = 0;
  332. }
  333. // we add the throttle_magic to the throttle value we send so we
  334. // can detect when we get it back
  335. throttle = ((uint32_t)(throttle * 1000)) * 1.0e-3f + throttle_magic;
  336. uint8_t flap_chan;
  337. if (SRV_Channels::find_channel(SRV_Channel::k_flap, flap_chan) ||
  338. SRV_Channels::find_channel(SRV_Channel::k_flap_auto, flap_chan)) {
  339. float flap = (input.servos[flap_chan]-1000)/1000.0;
  340. if (!is_equal(flap, last_flap)) {
  341. send_dref("sim/flightmodel/controls/flaprqst", flap);
  342. send_dref("sim/aircraft/overflow/acf_flap_arm", flap>0?1:0);
  343. }
  344. }
  345. d.code = FlightCon;
  346. d.data[0] = elevator;
  347. d.data[1] = aileron;
  348. d.data[2] = rudder;
  349. d.data[4] = rudder;
  350. socket_out.send(&d, sizeof(d));
  351. if (!heli_frame) {
  352. d.code = ThrottleCommand;
  353. d.data[0] = throttle;
  354. d.data[1] = throttle;
  355. d.data[2] = throttle;
  356. d.data[3] = throttle;
  357. d.data[4] = 0;
  358. socket_out.send(&d, sizeof(d));
  359. } else {
  360. // send chan3 as collective pitch, on scale from -10 to +10
  361. float collective = 10*(input.servos[2]-1500)/500.0;
  362. // and send throttle from channel 8
  363. throttle = (input.servos[7]-1000)/1000.0;
  364. // allow for extra throttle outputs for special aircraft
  365. float throttle2 = (input.servos[5]-1000)/1000.0;
  366. float throttle3 = (input.servos[6]-1000)/1000.0;
  367. d.code = PropPitch;
  368. d.data[0] = collective;
  369. d.data[1] = -rudder*15; // reverse sense of rudder, 15 degrees pitch range
  370. d.data[2] = 0;
  371. d.data[3] = 0;
  372. d.data[4] = 0;
  373. socket_out.send(&d, sizeof(d));
  374. d.code = ThrottleCommand;
  375. d.data[0] = throttle;
  376. d.data[1] = throttle;
  377. d.data[2] = throttle2;
  378. d.data[3] = throttle3;
  379. d.data[4] = 0;
  380. socket_out.send(&d, sizeof(d));
  381. }
  382. throttle_sent = throttle;
  383. }
  384. /*
  385. send DREF to X-Plane via UDP
  386. */
  387. void XPlane::send_dref(const char *name, float value)
  388. {
  389. struct PACKED {
  390. uint8_t marker[5] { 'D', 'R', 'E', 'F', '0' };
  391. float value;
  392. char name[500];
  393. } d {};
  394. d.value = value;
  395. strcpy(d.name, name);
  396. socket_out.send(&d, sizeof(d));
  397. }
  398. /*
  399. update the XPlane simulation by one time step
  400. */
  401. void XPlane::update(const struct sitl_input &input)
  402. {
  403. if (receive_data()) {
  404. send_data(input);
  405. }
  406. uint32_t now = AP_HAL::millis();
  407. if (report.last_report_ms == 0) {
  408. report.last_report_ms = now;
  409. }
  410. if (now - report.last_report_ms > 5000) {
  411. float dt = (now - report.last_report_ms) * 1.0e-3f;
  412. printf("Data rate: %.1f FPS Frame rate: %.1f FPS\n",
  413. report.data_count/dt, report.frame_count/dt);
  414. report.last_report_ms = now;
  415. report.data_count = 0;
  416. report.frame_count = 0;
  417. }
  418. }
  419. } // namespace SITL