364 changes: 364 additions & 0 deletions sw/simulator/nps/nps_hitl_sensors.c
@@ -0,0 +1,364 @@
/*
* Copyright (C) 2009 Antoine Drouin <poinix@gmail.com>
* Copyright (C) 2012 The Paparazzi Team
* Copyright (C) 2016 Michal Podhradsky <http://github.com/podhrmic>
* Copyright (C) 2023 Gautier Hattenberger <gautier.hattenberger@enac.fr>
*
* This file is part of paparazzi.
*
* paparazzi is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* paparazzi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with paparazzi; see the file COPYING. If not, write to
* the Free Software Foundation, 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/

#include "nps_ins.h"
#include <sys/time.h>
#include "nps_fdm.h"
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/poll.h>
#include "nps_sensors.h"
#include "nps_main.h"
#include "paparazzi.h"
#include "pprzlink/messages.h"
#include "pprzlink/dl_protocol.h"
#include "pprzlink/pprzlink_device.h"
#include "pprzlink/pprz_transport.h"
#include "arch/linux/serial_port.h"

#ifndef AP_DEV
#warning "[hitl] Please define AP_DEV in your airframe file"
#define AP_DEV "/dev/ttyUSB0"
#endif
#ifndef AP_BAUD
#define AP_BAUD B921600
PRINT_CONFIG_MSG_VALUE("[hitl] Using default baudrate for AP_DEV (B921600)", AP_BAUD)
#endif

#define NPS_HITL_DEBUG 0
#if NPS_HITL_DEBUG
#define DEBUG_PRINT printf
#else
#define DEBUG_PRINT(...) {}
#endif

void *nps_sensors_loop(void *data __attribute__((unused)));
void *nps_ap_data_loop(void *data __attribute__((unused)));
pthread_t th_sensors; // send sensors to AP
pthread_t th_ap_data; // receive commands from AP

/* implement pprzlink_device interface */
#define PPRZLINK_BUFFER_SIZE 256
struct linkdev {
/** Receive buffer */
uint8_t rx_buf[PPRZLINK_BUFFER_SIZE];
uint16_t rx_idx;
/** Transmit buffer */
uint8_t tx_buf[PPRZLINK_BUFFER_SIZE];
uint16_t tx_idx;
//volatile uint8_t tx_running;
/** Generic device interface */
struct link_device device;
/** transport */
struct pprz_transport pprz_tp;
/** Serial port */
struct SerialPort *port;
struct pollfd fds[1];
};

static struct linkdev dev;

static int check_free_space(struct linkdev *d, long *fd __attribute__((unused)), uint16_t len)
{
int space = PPRZLINK_BUFFER_SIZE - d->tx_idx;
return space >= len ? space : 0;
}

static void put_byte(struct linkdev *d, long fd __attribute__((unused)), uint8_t data)
{
d->tx_buf[d->tx_idx++] = data;
}

static void put_buffer(struct linkdev *d, long fd __attribute__((unused)), const uint8_t *data, uint16_t len)
{
memcpy(&(d->tx_buf[d->tx_idx]), data, len);
d->tx_idx += len;
}

static void send_message(struct linkdev *d, long fd __attribute__((unused)))
{
int ret = 0;
do {
ret = write((int)(d->port->fd), d->tx_buf, d->tx_idx);
} while (ret < 1 && errno == EAGAIN); //FIXME: max retry
if (ret < 1) {
DEBUG_PRINT("[hitl] put_byte: write failed [%d: %s]\n", ret, strerror(errno));
}
d->tx_idx = 0;
}

static uint8_t getch(struct linkdev *d)
{
// this function should only be called when bytes are available
unsigned char c = 'B';
ssize_t ret = 0;
ret = read(d->port->fd, &c, 1);
if (ret > 0) {
d->rx_buf[d->rx_idx] = c;
if (d->rx_idx < PPRZLINK_BUFFER_SIZE) {
d->rx_idx++;
} else {
DEBUG_PRINT("[hitl] rx buffer overflow\n");
}
} else {
DEBUG_PRINT("[hitl] rx read error\n");
}
return c;
}

static int char_available(struct linkdev *d)
{
int ret = poll(d->fds, 1, 1000);
if (ret > 0) {
if (d->fds[0].revents & POLLHUP) {
printf("[hitl] lost connection. Exiting\n");
exit(1);
}
if (d->fds[0].revents & POLLIN) {
return true;
}
} else if (ret == -1) {
DEBUG_PRINT("[hitl] poll failed\n");
}
return false;
}

/// END pprzlink_dev

void nps_hitl_impl_init(void)
{
pthread_create(&th_sensors, NULL, nps_sensors_loop, NULL);
pthread_create(&th_ap_data, NULL, nps_ap_data_loop, NULL);

dev.device.periph = (void *) (&dev);
dev.device.check_free_space = (check_free_space_t) check_free_space;
dev.device.put_byte = (put_byte_t) put_byte;
dev.device.put_buffer = (put_buffer_t) put_buffer;
dev.device.send_message = (send_message_t) send_message;
dev.device.char_available = (char_available_t) char_available;
dev.device.get_byte = (get_byte_t) getch;
pprz_transport_init(&dev.pprz_tp);

// open serial port
dev.port = serial_port_new();
int ret = serial_port_open_raw(dev.port, AP_DEV, AP_BAUD);
if (ret != 0) {
printf("[hitl] Error opening %s code %d\n", AP_DEV, ret);
serial_port_free(dev.port);
}

// poll
if (dev.port != NULL) {
dev.fds[0].fd = dev.port->fd;
dev.fds[0].events = POLLIN;
}
}

void *nps_sensors_loop(void *data __attribute__((unused)))
{
struct timespec requestStart;
struct timespec requestEnd;
struct timespec waitFor;
long int period_ns = (1. / PERIODIC_FREQUENCY) * 1000000000L; // thread period in nanoseconds
long int task_ns = 0; // time it took to finish the task in nanoseconds

while (TRUE) {
// lock mutex
pthread_mutex_lock(&fdm_mutex);

// start timing
clock_get_current_time(&requestStart);

uint8_t id = AC_ID;

if (nps_sensors_gyro_available()) {
float gx = (float)sensors.gyro.value.x;
float gy = (float)sensors.gyro.value.y;
float gz = (float)sensors.gyro.value.z;
float ax = (float)sensors.accel.value.x;
float ay = (float)sensors.accel.value.y;
float az = (float)sensors.accel.value.z;
float mx = (float)sensors.mag.value.x;
float my = (float)sensors.mag.value.y;
float mz = (float)sensors.mag.value.z;
uint8_t id = AC_ID;
pprz_msg_send_HITL_IMU(&dev.pprz_tp.trans_tx, &dev.device, 0,
&gx, &gy, &gz,
&ax, &ay, &az,
&mx, &my, &mz,
&id);
}

if (nps_sensors_gps_available()) {
float gps_lat = (float)DegOfRad(sensors.gps.lla_pos.lat);
float gps_lon = (float)DegOfRad(sensors.gps.lla_pos.lon);
float gps_alt = (float)sensors.gps.lla_pos.alt;
float gps_hmsl = (float)sensors.gps.hmsl;
float gps_vx = (float)sensors.gps.ecef_vel.x;
float gps_vy = (float)sensors.gps.ecef_vel.y;
float gps_vz = (float)sensors.gps.ecef_vel.z;
float gps_time = (float)nps_main.sim_time;
uint8_t gps_fix = 3; // GPS fix
pprz_msg_send_HITL_GPS(&dev.pprz_tp.trans_tx, &dev.device, 0,
&gps_lat, &gps_lon, &gps_alt, &gps_hmsl,
&gps_vx, &gps_vy, &gps_vz,
&gps_time, &gps_fix, &id);
}

uint8_t air_data_flag = 0;
float baro = -1.f;
float airspeed = -1.f;
float aoa = 0.f;
float sideslip = 0.f;
if (nps_sensors_baro_available()) {
SetBit(air_data_flag, 0);
baro = (float) sensors.baro.value;
}
if (nps_sensors_airspeed_available()) {
SetBit(air_data_flag, 1);
airspeed = (float) sensors.airspeed.value;
}
if (nps_sensors_aoa_available()) {
SetBit(air_data_flag, 2);
aoa = (float) sensors.aoa.value;
}
if (nps_sensors_sideslip_available()) {
SetBit(air_data_flag, 3);
sideslip = (float) sensors.sideslip.value;
}
if (air_data_flag != 0) {
pprz_msg_send_HITL_AIR_DATA(&dev.pprz_tp.trans_tx, &dev.device, 0,
&baro, &airspeed, &aoa, &sideslip, &air_data_flag, &id);
}

// unlock mutex
pthread_mutex_unlock(&fdm_mutex);

clock_get_current_time(&requestEnd);

// Calculate time it took
task_ns = (requestEnd.tv_sec - requestStart.tv_sec) * 1000000000L + (requestEnd.tv_nsec - requestStart.tv_nsec);

// task took less than one period, sleep for the rest of time
if (task_ns < period_ns) {
waitFor.tv_sec = 0;
waitFor.tv_nsec = period_ns - task_ns;
nanosleep(&waitFor, NULL);
} else {
// task took longer than the period
#ifdef PRINT_TIME
printf("SENSORS: task took longer than one period, exactly %f [ms], but the period is %f [ms]\n",
(double)task_ns / 1E6, (double)period_ns / 1E6);
#endif
}
}
return(NULL);
}

void *nps_ap_data_loop(void *data __attribute__((unused)))
{
struct timespec waitFor;

uint8_t msg_buffer[PPRZLINK_BUFFER_SIZE];
bool msg_available = false;

bool first_command = true;

uint8_t cmd_len = 0;
pprz_t cmd_buf[NPS_COMMANDS_NB];

while (TRUE) {

pprz_check_and_parse(&dev.device, &dev.pprz_tp, msg_buffer, &msg_available);
if (msg_available) {
// reset rx index to zero for next message
dev.rx_idx = 0;
//Parse message
uint8_t sender_id = SenderIdOfPprzMsg(msg_buffer);
uint8_t msg_id = IdOfPprzMsg(msg_buffer);

if (sender_id != AC_ID) {
printf("[hitl] receiving message from wrong id (%d)\n", sender_id);
return(NULL); // wrong A/C ?
}
/* parse telemetry messages coming from the correct AC_ID */
switch (msg_id) {
case DL_COMMANDS:
// parse commands message
cmd_len = DL_COMMANDS_values_length(msg_buffer);
memcpy(&cmd_buf, DL_COMMANDS_values(msg_buffer), cmd_len * sizeof(int16_t));
pthread_mutex_lock(&fdm_mutex);
// update commands
for (uint8_t i = 0; i < NPS_COMMANDS_NB; i++) {
nps_autopilot.commands[i] = (double)cmd_buf[i] / MAX_PPRZ;
}
// hack: invert pitch to fit most JSBSim models
nps_autopilot.commands[COMMAND_PITCH] = -(double)cmd_buf[COMMAND_PITCH] / MAX_PPRZ;
if (first_command) {
printf("[hitl] receiving COMMANDS message\n");
first_command = false;
}
pthread_mutex_unlock(&fdm_mutex);
break;
case DL_MOTOR_MIXING:
// parse actuarors message
cmd_len = DL_MOTOR_MIXING_values_length(msg_buffer);
// check for out-of-bounds access
if (cmd_len > NPS_COMMANDS_NB) {
cmd_len = NPS_COMMANDS_NB;
}
memcpy(&cmd_buf, DL_MOTOR_MIXING_values(msg_buffer), cmd_len * sizeof(int16_t));
pthread_mutex_lock(&fdm_mutex);
// update commands
for (uint8_t i = 0; i < NPS_COMMANDS_NB; i++) {
nps_autopilot.commands[i] = (double)cmd_buf[i] / MAX_PPRZ;
}
if (first_command) {
printf("[hitl] receiving MOTOR_MIXING message\n");
first_command = false;
}
pthread_mutex_unlock(&fdm_mutex);
break;
default:
break;
}

msg_available = false;
}

// wait before next loop
waitFor.tv_sec = 0;
waitFor.tv_nsec = 1000;
nanosleep(&waitFor, NULL);

}
return(NULL);
}


290 changes: 0 additions & 290 deletions sw/simulator/nps/nps_ins_vectornav.c

This file was deleted.

8 changes: 8 additions & 0 deletions sw/simulator/nps/nps_ivy.c
Expand Up @@ -160,6 +160,10 @@ void nps_ivy_send_WORLD_ENV_REQ(void)

int find_launch_index(void)
{
#ifdef AP_LAUNCH
return AP_LAUNCH - 1; // index of AP_LAUNCH starts at 1, but it should be 0 here
#else
#if NB_SETTING > 0
static const char ap_launch[] = "aut_lau"; // short name
char *ap_settings[NB_SETTING] = SETTINGS_NAMES_SHORT;

Expand All @@ -170,7 +174,9 @@ int find_launch_index(void)
return (int)idx;
}
}
#endif
return -1;
#endif
}

static void on_DL_SETTING(IvyClientPtr app __attribute__((unused)),
Expand All @@ -190,10 +196,12 @@ static void on_DL_SETTING(IvyClientPtr app __attribute__((unused)),
*/
uint8_t index = atoi(argv[2]);
float value = atof(argv[3]);
#ifndef HITL
if (!datalink_enabled) {
DlSetting(index, value);
DOWNLINK_SEND_DL_VALUE(DefaultChannel, DefaultDevice, &index, &value);
}
#endif
printf("setting %d %f\n", index, value);

/*
Expand Down
1 change: 1 addition & 0 deletions sw/simulator/nps/nps_ivy.h
Expand Up @@ -7,6 +7,7 @@
extern bool nps_ivy_send_world_env;

extern void nps_ivy_init(char *ivy_bus);
extern void nps_ivy_hitl(struct NpsSensors* sensors_data);
extern void nps_ivy_display(struct NpsFdm* fdm_ivy, struct NpsSensors* sensors_ivy);
extern void nps_ivy_send_WORLD_ENV_REQ(void);

Expand Down
2 changes: 2 additions & 0 deletions sw/simulator/nps/nps_main.h
Expand Up @@ -48,6 +48,8 @@ extern double ntime_to_double(struct timespec *t);

void nps_update_launch_from_dl(uint8_t value);

extern void nps_hitl_impl_init(void); // implement for HITL specific implementation

struct NpsMain {
double real_initial_time;
double scaled_initial_time;
Expand Down
235 changes: 4 additions & 231 deletions sw/simulator/nps/nps_main_hitl.c
Expand Up @@ -26,68 +26,37 @@
#include <string.h>
#include <time.h>

#include "termios.h"
#include <fcntl.h>
#include <unistd.h>


#include "paparazzi.h"
#include "pprzlink/messages.h"
#include "pprzlink/dl_protocol.h"
#include "pprzlink/pprz_transport.h"
#include "generated/airframe.h"


#include "nps_main.h"
#include "nps_sensors.h"
#include "nps_atmosphere.h"
#include "nps_autopilot.h"
#include "nps_ivy.h"
#include "nps_flightgear.h"
#include "mcu_periph/sys_time.h"

#include "nps_ins.h"

void *nps_ins_data_loop(void *data __attribute__((unused)));
void *nps_ap_data_loop(void *data __attribute__((unused)));

pthread_t th_ins_data; // sends INS packets to the autopilot
pthread_t th_ap_data; // receives commands from the autopilot

#define NPS_MAX_MSG_SIZE 512
// nps_autopilot.c is not compiled in HITL
struct NpsAutopilot nps_autopilot;

int main(int argc, char **argv)
{
nps_main_init(argc, argv);

nps_hitl_impl_init();

if (nps_main.fg_host) {
pthread_create(&th_flight_gear, NULL, nps_flight_gear_loop, NULL);
}
pthread_create(&th_display_ivy, NULL, nps_main_display, NULL);
pthread_create(&th_main_loop, NULL, nps_main_loop, NULL);
pthread_create(&th_ins_data, NULL, nps_ins_data_loop, NULL);
pthread_create(&th_ap_data, NULL, nps_ap_data_loop, NULL);
pthread_join(th_main_loop, NULL);

return 0;
}

void nps_radio_and_autopilot_init(void)
{
enum NpsRadioControlType rc_type;
char *rc_dev = NULL;
if (nps_main.norc) {
rc_type = NORC;
} else if (nps_main.js_dev) {
rc_type = JOYSTICK;
rc_dev = nps_main.js_dev;
} else if (nps_main.spektrum_dev) {
rc_type = SPEKTRUM;
rc_dev = nps_main.spektrum_dev;
} else {
rc_type = SCRIPT;
}
nps_autopilot_init(rc_type, nps_main.rc_script, rc_dev);
}

void nps_update_launch_from_dl(uint8_t value)
Expand All @@ -105,202 +74,6 @@ void nps_main_run_sim_step(void)
nps_sensors_run_step(nps_main.sim_time);
}

void *nps_ins_data_loop(void *data __attribute__((unused)))
{
struct timespec requestStart;
struct timespec requestEnd;
struct timespec waitFor;
long int period_ns = (1. / INS_FREQUENCY) * 1000000000L; // thread period in nanoseconds
long int task_ns = 0; // time it took to finish the task in nanoseconds

nps_ins_init(); // initialize ins variables and pointers

// configure port
int fd = open(STRINGIFY(INS_DEV), O_WRONLY | O_NOCTTY | O_SYNC);//open(INS_DEV, O_RDWR | O_NOCTTY);
if (fd < 0) {
printf("INS THREAD: data loop error opening port %i\n", fd);
return(NULL);
}

struct termios new_settings;
tcgetattr(fd, &new_settings);
memset(&new_settings, 0, sizeof(new_settings));
new_settings.c_iflag = 0;
new_settings.c_cflag = 0;
new_settings.c_lflag = 0;
new_settings.c_cc[VMIN] = 0;
new_settings.c_cc[VTIME] = 0;
cfsetispeed(&new_settings, (speed_t)INS_BAUD);
cfsetospeed(&new_settings, (speed_t)INS_BAUD);
tcsetattr(fd, TCSANOW, &new_settings);

struct NpsFdm fdm_ins;

while (TRUE) {
// lock mutex
pthread_mutex_lock(&fdm_mutex);

// start timing
clock_get_current_time(&requestStart);

// make a copy of fdm struct to speed things up
memcpy(&fdm_ins, &fdm, sizeof(fdm));

// unlock mutex
pthread_mutex_unlock(&fdm_mutex);

// fetch data
nps_ins_fetch_data(&fdm_ins);

// send ins data here
static uint16_t idx;
idx = nps_ins_fill_buffer();

static int wlen;
wlen = write(fd, ins_buffer, idx);
if (wlen != idx) {
printf("INS THREAD: Warning - sent only %u bytes to the autopilot, instead of expected %u\n", wlen, idx);
}

clock_get_current_time(&requestEnd);

// Calculate time it took
task_ns = (requestEnd.tv_sec - requestStart.tv_sec) * 1000000000L + (requestEnd.tv_nsec - requestStart.tv_nsec);

// task took less than one period, sleep for the rest of time
if (task_ns < period_ns) {
waitFor.tv_sec = 0;
waitFor.tv_nsec = period_ns - task_ns;
nanosleep(&waitFor, NULL);
} else {
// task took longer than the period
#ifdef PRINT_TIME
printf("INS THREAD: task took longer than one period, exactly %f [ms], but the period is %f [ms]\n",
(double)task_ns / 1E6, (double)period_ns / 1E6);
#endif
}
}
return(NULL);
}



void *nps_ap_data_loop(void *data __attribute__((unused)))
{
// configure port
int fd = open(STRINGIFY(AP_DEV), O_RDONLY | O_NOCTTY);
if (fd < 0) {
printf("AP data loop error opening port %i\n", fd);
return(NULL);
}

struct termios new_settings;
tcgetattr(fd, &new_settings);
memset(&new_settings, 0, sizeof(new_settings));
new_settings.c_iflag = 0;
new_settings.c_cflag = 0;
new_settings.c_lflag = 0;
new_settings.c_cc[VMIN] = 1;
new_settings.c_cc[VTIME] = 5;
cfsetispeed(&new_settings, (speed_t)AP_BAUD);
cfsetospeed(&new_settings, (speed_t)AP_BAUD);
tcsetattr(fd, TCSANOW, &new_settings);

int rdlen;
uint8_t buf[NPS_MAX_MSG_SIZE];
uint8_t cmd_len;
pprz_t cmd_buf[NPS_COMMANDS_NB];

struct pprz_transport pprz_tp_logger;

pprz_transport_init(&pprz_tp_logger);
uint32_t rx_msgs = 0;
uint32_t rx_commands = 0;
uint32_t rx_motor_mixing = 0;
uint8_t show_stats = 1;

while (TRUE) {
if ((rx_msgs % 100 == 0) && show_stats && (rx_msgs > 0) ) {
printf("AP data loop received %i messages.\n", rx_msgs);
printf("From those, %i were COMMANDS messages, and %i were MOTOR_MIXING messages.\n",
rx_commands, rx_motor_mixing);
show_stats = 0;
}

// receive messages from the autopilot
// Note: read function might wait until the buffer is full, in which case
// it could contain several messages. That might lead to delay in processing,
// for example if we send COMMANDS at 100Hz, and each read() will hold 10 COMMANDS
// it means a delay of 100ms before the message is processed.
rdlen = read(fd, buf, sizeof(buf) - 1);

for (int i = 0; i < rdlen; i++) {
// parse data
parse_pprz(&pprz_tp_logger, buf[i]);

// if msg_available then read
if (pprz_tp_logger.trans_rx.msg_received) {
rx_msgs++;
for (int k = 0; k < pprz_tp_logger.trans_rx.payload_len; k++) {
buf[k] = pprz_tp_logger.trans_rx.payload[k];
}
//Parse message
uint8_t sender_id = SenderIdOfPprzMsg(buf);
uint8_t msg_id = IdOfPprzMsg(buf);

// parse telemetry messages coming from other AC
if (sender_id != AC_ID) {
switch (msg_id) {
default: {
break;
}
}
} else {
/* parse telemetry messages coming from the correct AC_ID */
switch (msg_id) {
case DL_COMMANDS:
// parse commands message
rx_commands++;
cmd_len = DL_COMMANDS_values_length(buf);
memcpy(&cmd_buf, DL_COMMANDS_values(buf), cmd_len * sizeof(int16_t));
pthread_mutex_lock(&fdm_mutex);
// update commands
for (uint8_t i = 0; i < NPS_COMMANDS_NB; i++) {
nps_autopilot.commands[i] = (double)cmd_buf[i] / MAX_PPRZ;
}
// hack: invert pitch to fit most JSBSim models
nps_autopilot.commands[COMMAND_PITCH] = -(double)cmd_buf[COMMAND_PITCH] / MAX_PPRZ;
pthread_mutex_unlock(&fdm_mutex);
break;
case DL_MOTOR_MIXING:
// parse actuarors message
rx_motor_mixing++;
cmd_len = DL_MOTOR_MIXING_values_length(buf);
// check for out-of-bounds access
if (cmd_len > NPS_COMMANDS_NB) {
cmd_len = NPS_COMMANDS_NB;
}
memcpy(&cmd_buf, DL_MOTOR_MIXING_values(buf), cmd_len * sizeof(int16_t));
pthread_mutex_lock(&fdm_mutex);
// update commands
for (uint8_t i = 0; i < NPS_COMMANDS_NB; i++) {
nps_autopilot.commands[i] = (double)cmd_buf[i] / MAX_PPRZ;
}
pthread_mutex_unlock(&fdm_mutex);
break;
default:
break;
}
}
pprz_tp_logger.trans_rx.msg_received = false;
}
}
}
return(NULL);
}




void *nps_main_loop(void *data __attribute__((unused)))
{
Expand Down
11 changes: 9 additions & 2 deletions sw/tools/generators/gen_aircraft.ml
Expand Up @@ -342,15 +342,22 @@ let () =
| _ -> Printf.eprintf "Missing airframe or flight_plan" end;

(* Create Makefile.ac only if needed *)
Printf.printf "Dumping makefile...%!";
let makefile_ac = aircraft_dir // "Makefile.ac" in
match ac.Aircraft.airframe with
| None -> ()
| None -> Printf.printf "(skip)"
| Some airframe ->
let module_files = List.map (fun m -> m.Module.xml_filename) loaded_modules in
if is_older makefile_ac (airframe.Airframe.filename :: module_files)
then
assert(Sys.command (sprintf "mv %s %s" temp_makefile_ac makefile_ac) = 0)
begin
Printf.printf("(copying)");
assert(Sys.command (sprintf "mv %s %s" temp_makefile_ac makefile_ac) = 0);
end
else
Printf.printf("(skip copying)");
;
Printf.printf " done\n%!";

with Failure f ->
prerr_endline f;
Expand Down