1,571 changes: 1,571 additions & 0 deletions sw/airborne/modules/ins/ins_flow.c

Large diffs are not rendered by default.

169 changes: 169 additions & 0 deletions sw/airborne/modules/ins/ins_flow.h
@@ -0,0 +1,169 @@
/*
* Copyright (C) 2021 Guido de Croon <g.c.h.e.decroon@tudelft.nl>
*
* 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.
*/

/**
* @file subsystems/ins/ins_flow.h
*
* "Inertial" navigation system.
*/

#ifndef INS_FLOW_H
#define INS_FLOW_H

#ifdef __cplusplus
extern "C" {
#endif

#define CONSTANT_ALT_FILTER 1
#define OF_DRAG 1
// Only for constant alt for now!
#define OF_TWO_DIM 1
// Only for changing alt
#define OF_THRUST_BIAS 0
// Whether to use gyros:
#define OF_USE_GYROS 1
// Predicting gyros with roll command and optic flow (normally 0):
#define PREDICT_GYROS 0

#if CONSTANT_ALT_FILTER == 1

#if OF_TWO_DIM == 0


#define OF_V_IND 0
#define OF_ANGLE_IND 1
#define OF_Z_IND 2
#define OF_ANGLE_DOT_IND 3
#if OF_USE_GYROS == 1
#define N_MEAS_OF_KF 3
#else
// gyros not used at all
#define N_MEAS_OF_KF 2
#endif

#define OF_THETA_IND -1
#define OF_VX_IND -1

#if OF_THRUST_BIAS == 0
#define N_STATES_OF_KF 4
#define OF_THRUST_BIAS_IND -1
#else
// does this work with thrust bias?
#define N_STATES_OF_KF 5
#define OF_THRUST_BIAS_IND 4
#endif

#else
#define N_STATES_OF_KF 6
#define OF_V_IND 0
#define OF_ANGLE_IND 1
#define OF_Z_IND 2
#define OF_THETA_IND 3
#define OF_VX_IND 4
#define OF_ANGLE_DOT_IND 5
// TODO: also a theta dot ind?


#define OF_THRUST_BIAS_IND -1

// the third measurement here is the other lateral flow:
#if OF_USE_GYROS == 1
// gyros used in the prediction and measurement
#define N_MEAS_OF_KF 4
#else
// gyros not used at all
#define N_MEAS_OF_KF 3
#endif
#endif

#define OF_Z_DOT_IND -1
#else
#if OF_THRUST_BIAS == 0
#define N_STATES_OF_KF 5
#define OF_THRUST_BIAS_IND -1
#else
#define N_STATES_OF_KF 6
#define OF_THRUST_BIAS_IND 5
#endif

#define OF_V_IND 0
#define OF_ANGLE_IND 1
#define OF_ANGLE_DOT_IND 2
#define OF_Z_IND 3
#define OF_Z_DOT_IND 4

#define OF_THETA_IND -1
#define OF_VX_IND -1


#if OF_USE_GYROS == 1
// gyros used in the prediction and measurement
#define N_MEAS_OF_KF 3
#else
// gyros not used at all
#define N_MEAS_OF_KF 2
#endif

#endif

// TODO: make these parameters in the estimation scheme:
#define OF_TB_Q 0.02
#define OF_TB_P 0.5


#define OF_LAT_FLOW_IND 0
#define OF_DIV_FLOW_IND 1
#define OF_RATE_IND 2
#if OF_USE_GYROS == 1
#define OF_LAT_FLOW_X_IND 3
#else
#define OF_LAT_FLOW_X_IND 2
#endif


// use filter to different extents:
#define USE_ANGLE 1
#define USE_VELOCITY 2
#define USE_HEIGHT 3


#include "modules/ahrs/ahrs.h"
#include "modules/ahrs/ahrs_int_cmpl_quat.h"
#include "modules/ahrs/ahrs_aligner.h"
#include "modules/ins/ins.h"

extern void ins_flow_init(void);
extern void ins_flow_update(void);

extern float OF_X[N_STATES_OF_KF];
extern bool reset_filter;
extern bool run_filter;
extern int use_filter;
extern float thrust_factor;
extern float GT_phi;
extern float GT_theta;


#ifdef __cplusplus
}
#endif

#endif /* INS_FLOW_H */
68 changes: 68 additions & 0 deletions sw/airborne/modules/sensors/generic_uart.c
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2020 Freek van Tienen <freek.v.tienen@gmail.com>
*
* 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, see
* <http://www.gnu.org/licenses/>.
*/

/**
* @file "modules/sensors/generic_uart.c"
* @author F. van Tienen
* Generic UART sensor, forwarding towards the GCS through telemetry
*/

#include "modules/sensors/generic_uart.h"
#include "modules/datalink/telemetry.h"
#include "mcu_periph/uart.h"

/* Default end character */
#ifndef GENERIC_UART_ENDCHAR
#define GENERIC_UART_ENDCHAR '>'
#endif

/* Main variables */
static struct link_device *gen_uart_dev = (&((GENERIC_UART_PORT).device)); ///< UART device for communication

/* Event function to read UART message and forward to downlink */
void generic_uart_event(void) {
// Receive buffer
static uint8_t gen_msg_buf[128];
static uint8_t gen_msg_cnt = 0;

// Transmit buffer
static uint8_t msg_buf_snd[128];
static uint8_t msg_cnt_snd = 0;

// Look for data on serial port and save it in the buffer
while (gen_uart_dev->char_available(gen_uart_dev->periph)) {
gen_msg_buf[gen_msg_cnt++] = gen_uart_dev->get_byte(gen_uart_dev->periph);

if(gen_msg_buf[gen_msg_cnt-1] == GENERIC_UART_ENDCHAR)
break;
}

// Forward the message to the GCS
if(gen_msg_buf[gen_msg_cnt-1] == GENERIC_UART_ENDCHAR || gen_msg_cnt > 50) {
msg_cnt_snd = gen_msg_cnt;
for(uint8_t i = 0; i < msg_cnt_snd; ++i)
msg_buf_snd[i] = gen_msg_buf[i];

DOWNLINK_SEND_PAYLOAD(DefaultChannel, DefaultDevice, msg_cnt_snd, msg_buf_snd);

gen_msg_buf[0] = 0;
gen_msg_cnt = 0;
}
}
34 changes: 34 additions & 0 deletions sw/airborne/modules/sensors/generic_uart.h
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2020 Freek van Tienen <freek.v.tienen@gmail.com>
*
* 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, see
* <http://www.gnu.org/licenses/>.
*/

/**
* @file "modules/sensors/generic_uart.c"
* @author F. van Tienen
* Generic UART sensor, forwarding towards the GCS through telemetry
*/

#ifndef GENERIC_UART_H
#define GENERIC_UART_H

#include "std.h"

extern void generic_uart_event(void);

#endif
2 changes: 1 addition & 1 deletion sw/ext/pprzlink
89 changes: 72 additions & 17 deletions sw/ground_segment/python/fuelcell/fuel_cell_viewer.py
Expand Up @@ -22,7 +22,7 @@
import sys
import os
import math
import datetime
from datetime import datetime


PPRZ_HOME = os.getenv("PAPARAZZI_HOME", os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)),
Expand Down Expand Up @@ -53,6 +53,7 @@ def __init__(self, msg):
self.current = float(msg['current'])
self.power = float(msg['power'])
self.energy = float(msg['energy'])
self.averagepower = float(msg['avg_power'])

class TempMessage(object):
def __init__(self, msg):
Expand All @@ -61,13 +62,15 @@ def __init__(self, msg):

class EscMessage(object):
def __init__(self, msg):
self.id = int(msg['motor_id'])
self.id = int(msg['node_id']) # motor_id
self.amp = float(msg['amps'])
self.rpm = float(msg['rpm'])
self.volt_b = float(msg['bat_volts'])
self.volt_m = float(msg['motor_volts'])
self.temperature = float(msg['power']) - 273.15
self.energy = float(msg['energy'])
self.temperature = float(msg['temperature']) - 273.5
self.energy = float(msg['amps']) * float(msg['motor_volts'])
self.errors = float(msg['energy'])
self.msgtime = datetime.now()

def get_current(self):
return str(round(self.amp ,1)) + "A"
Expand All @@ -77,18 +80,28 @@ def get_current_perc(self):
def get_rpm(self):
return str(round(self.rpm ,0)) + " rpm"
def get_rpm_perc(self):
return self.rpm / 4000
return self.rpm / 7000
def get_rpm_color(self):
if self.rpm < 2500:
if self.rpm < 4800:
return 1
return 0.5

def get_err(self):
return str(self.errors) + " errs"
def get_err_perc(self):
return 0
def get_err_color(self):
return 0.5

def get_volt(self):
if (self.id in [6,7,8,9,16,17,18,19]):
if (self.id in [10,17]):
return "Servo " + str(self.id) + " " +str(round(self.volt_m ,1)) + "V"
elif (self.id in [1,6]):
return "Old " + str(self.id) + " " +str(round(self.volt_m ,1)) + "V"
elif (self.id in [2,3,4,5,11,12,13,14,15,16]):
return "New " + str(self.id) + " " +str(round(self.volt_m ,1)) + "V"
else:
return "Mot " + str(self.id) + " " +str(round(self.volt_m ,1)) + "V"
return "? " + str(self.id) + " " +str(round(self.volt_m ,1)) + "V"
def get_volt_perc(self):
return self.volt_b / (6*4.2)

Expand All @@ -104,8 +117,20 @@ def get_temp_color(self):
return 0
elif self.temperature < 60:
return 1
else:
elif self.temperature < 90:
return 0.5
else:
return -1

def get_timestamp(self, CPUTIME):
now = datetime.now()
return 'Age: '+str(round((now - self.msgtime).total_seconds(),1))+'s'

def get_timestamp_color(self, CPUTIME):
now = datetime.now()
if ((now - self.msgtime).total_seconds()) > 30:
return 0
return 1

class MotorList(object):
def __init__(self):
Expand All @@ -127,6 +152,7 @@ def __init__(self):
self.voltage = 0
self.current = 0
self.power = 0
self.averagepower = 0
self.energy = 0
self.model = 0
self.temperature = 0
Expand All @@ -135,6 +161,7 @@ def fill_from_energy_msg(self, energy):
self.current = energy.current
self.power = energy.power
self.energy = energy.energy
self.averagepower = energy.averagepower
self.model = 0
def fill_from_temp_msg(self, temp):
self.temperature = temp.battery
Expand All @@ -150,6 +177,8 @@ def get_temp(self):
return "Cell Temp = "+str(round(self.temperature ,2))
def get_power_text(self):
return "Battery Power: {:.0f}W".format(self.get_power())
def get_averagepower_text(self):
return "Average Power: {:.0f}W".format(self.get_averagepower())
def get_power2_text(self):
return "Battery Power: {:.0f}W".format(self.get_power2())
def get_volt_perc(self):
Expand All @@ -159,6 +188,8 @@ def get_volt_percent(self,volt):

def get_power(self):
return self.power
def get_averagepower(self):
return self.averagepower
def get_power2(self):
return self.voltage * self.current

Expand All @@ -171,6 +202,8 @@ def get_energy_perc(self):

def get_power_perc(self):
return (self.get_power()) / (2500)
def get_averagepower_perc(self):
return (self.get_averagepower()) / (2500)

def get_volt_color(self):
if self.voltage < 3.2:
Expand All @@ -192,7 +225,14 @@ def get_energy_color(self):
elif self.energy < 2000:
return 1
return 0.5


def get_averagepower_color(self):
if self.energy > 3000:
return 0.1
elif self.energy < 2000:
return 1
return 0.5

def get_temp_color(self):
if (self.temperature > 20) & (self.temperature < 40):
return 1
Expand All @@ -215,9 +255,11 @@ def __init__(self, msg):
class FuelCellStatus(object):
def _init_(self):
self.blink = 0
self.msgtime = 0

def update(self,msg):
self.msg = msg
self.msgtime = datetime.now()
if hasattr(self, 'blink'):
self.blink = 1 - self.blink
else:
Expand All @@ -239,6 +281,9 @@ def update(self,msg):
#else:
# print('ERROR: ' + msg)

def get_age(self):
now = datetime.now()
return 'Age: '+str(round((now - self.msgtime).total_seconds(),1))+'s'
def get_raw(self):
return self.msg
def get_raw_color(self):
Expand All @@ -248,7 +293,7 @@ def get_raw_color(self):

def get_tank(self):
bar = round(5 + self.tank / 100 * 295,1)
return 'Cylinder ' + str(bar) + ' Bar'
return 'Cylinder ' + str(bar) + ' Bar ('+str(self.tank)+'%)'
def get_tank_perc(self):
return (self.tank) / 100.0
def get_tank_color(self):
Expand All @@ -261,7 +306,7 @@ def get_tank_color(self):

def get_battery(self):
volt = round( self.battery / 100.0 * (24.0-19.6) + 19.6, 2)
return str(volt) + ' V'
return str(volt) + ' V ('+str(self.battery)+'%)'
def get_battery_perc(self):
return (self.battery) / 100.0
def get_battery_color(self):
Expand Down Expand Up @@ -346,6 +391,10 @@ def message_recv(self, ac_id, msg):
self.fuelcell.update(self.payload.values)
#print("Payload: " + self.payload.values)

elif msg.name == "ROTORCRAFT_STATUS":
self.CPUTIME = float(msg['cpu_time'])



def update(self):
self.Refresh()
Expand All @@ -372,9 +421,9 @@ def StatusBox(self, dc, dx, dy, row, col, txt, percent, color):
boxw = self.stat
tdx = int(boxw * 10.0 / 300.0)
tdy = int(boxw * 6.0 / 300.0)
boxh = int(boxw * 40.0 / 300.0)
boxh = int(boxw * 45.0 / 300.0)
boxw = self.stat - 2*tdx
spacing = boxh+10
spacing = boxh+3

dc.SetPen(wx.Pen(wx.Colour(0,0,0)))
dc.SetBrush(wx.Brush(wx.Colour(220,220,220)))
Expand Down Expand Up @@ -443,6 +492,7 @@ def OnPaint(self, e):
self.StatusBox(dc, dx, dy, 1, 0, self.cell.get_current(), self.cell.get_current_perc(), self.cell.get_current_color() )
self.StatusBox(dc, dx, dy, 2, 0, self.cell.get_power_text(), self.cell.get_power_perc(), self.cell.get_power_color())
self.StatusBox(dc, dx, dy, 3, 0, self.cell.get_energy(), self.cell.get_energy_perc(), self.cell.get_energy_color() )
self.StatusBox(dc, dx, dy, 4, 0, self.cell.get_averagepower_text(), self.cell.get_averagepower_perc(), self.cell.get_averagepower_color() )

dx = int(0.43*w)
dy = int(0.15*h)
Expand All @@ -451,6 +501,7 @@ def OnPaint(self, e):
self.StatusBox(dc, dx, dy, 2, 0, self.fuelcell.get_battery(), self.fuelcell.get_battery_perc(), self.fuelcell.get_battery_color())
self.StatusBox(dc, dx, dy, 3, 0, self.fuelcell.get_status(), self.fuelcell.get_status_perc(), self.fuelcell.get_status_color())
self.StatusBox(dc, dx, dy, 4, 0, self.fuelcell.get_error(), self.fuelcell.get_error_perc(), self.fuelcell.get_error_color())
self.StatusBox(dc, dx, dy, 5, 0, self.fuelcell.get_age(), 0, 1)

# Warnings
self.stat = int(0.14*w)
Expand All @@ -470,20 +521,22 @@ def OnPaint(self, e):
w2 = 0.60
dw = 0.11
mw = 0.1
mm = [(0.03,w1), (0.03+dw,w1), (0.03+2*dw,w1), (0.97-mw-2*dw,w1), (0.97-mw-dw,w1), (0.97-mw,w1), (0.03,w1+0.17), (0.03+dw,w1+0.17), (0.97-mw-dw,w1+0.17), (0.97-mw,w1+0.17),
(0.03,w2), (0.03+dw,w2), (0.03+2*dw,w2), (0.97-mw-2*dw,w2), (0.97-mw-dw,w2), (0.97-mw,w2), (0.03,w2+0.17), (0.03+dw,w2+0.17), (0.97-mw-dw,w2+0.17), (0.97-mw,w2+0.17)]
mm = [(0.03,w1), (0.03+dw,w1), (0.03+2*dw,w1), (0.97-mw-2*dw,w1), (0.97-mw-dw,w1), (0.97-mw,w1), (-100,0), (-100,0), (-100,0), (0.03,w2+0.19),
(0.03,w2), (0.03+dw,w2), (0.03+2*dw,w2), (0.97-mw-2*dw,w2), (0.97-mw-dw,w2), (0.97-mw,w2), (0.97-mw,w2+0.19), (-100,0), (-100,0), (-100,0)]
for m in mm:
dc.DrawRectangle(int(m[0]*w), int(m[1]*h),int(mw*w), int(0.15*h))

for m in self.motors.mot:
mo_co = mm[m.id]
mo_co = mm[m.id-1]
#print(m.id, mo_co)
dx = int(mo_co[0]*w)
dy = int(mo_co[1]*h)
self.StatusBox(dc, dx, dy, 0, 0, m.get_volt(), m.get_volt_perc(), 1)
self.StatusBox(dc, dx, dy, 1, 0, m.get_current(), m.get_current_perc(), 1)
self.StatusBox(dc, dx, dy, 2, 0, m.get_rpm(), m.get_rpm_perc(), m.get_rpm_color())
self.StatusBox(dc, dx, dy, 3, 0, m.get_temp(), m.get_temp_perc(), m.get_temp_color())
self.StatusBox(dc, dx, dy, 4, 0, m.get_err(), m.get_err_perc(), m.get_err_color())
self.StatusBox(dc, dx, dy, 5, 0, m.get_timestamp(self.CPUTIME), m.get_err_perc(), m.get_timestamp_color(self.CPUTIME))



Expand Down Expand Up @@ -513,6 +566,7 @@ def __init__(self):
ico = wx.Icon(PPRZ_SRC + "/sw/ground_segment/python/energy_mon/energy_mon.ico", wx.BITMAP_TYPE_ICO)
self.SetIcon(ico)

self.CPUTIME = 0
self.bat = {}
self.temp = {}
self.cell = BatteryCell()
Expand Down Expand Up @@ -546,3 +600,4 @@ def OnClose(self, event):
#
# plt.plot(energies, seconds_left)
# plt.show()

137 changes: 137 additions & 0 deletions sw/ground_segment/python/fuelcell/fuel_tank_plotter.py
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
#
# Copyright (C) 2012 TUDelft
#
# 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 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>.
#


import numpy as np
import matplotlib.pyplot as plt

import sys
import os
from datetime import datetime
import time

PPRZ_HOME = os.getenv("PAPARAZZI_HOME", os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'../../../..')))

PPRZ_SRC = os.getenv("PAPARAZZI_SRC", os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'../../../..')))

sys.path.append(PPRZ_HOME + "/var/lib/python")

from pprzlink.ivy import IvyMessagesInterface

class PayloadMessage(object):
def __init__(self, msg):
self.values = ''.join(chr(int(x)) for x in msg['values'])

class TankPlotter(object):
def __init__(self):
self.interface = IvyMessagesInterface("fueltankplotter")
self.interface.subscribe(self.message_recv)
self.start_time = time.mktime(datetime.now().timetuple())
self.timestamps = []
self.pressures = []

def message_recv(self, ac_id, msg):
if msg.name == "PAYLOAD":
self.payload = PayloadMessage(msg)
self.update(self.payload.values)

def update(self,msg):
self.msg = msg
self.msgtime = datetime.now()
elements = self.msg.strip('<').strip('>').split(',')
if (len(elements) == 4):
self.tank = float(elements[0])
self.bar = round(5 + self.tank / 100 * 295,1)
self.battery = float(elements[1])
self.status = elements[2]
self.error = elements[3]

self.errors = []
hex = '0000'
if (len(self.error) >= 4):
hex = self.error[2:6]
#array of 16 error codes
self.error_bin = bin(int(hex, 16))[2:].zfill(16)

self.update_plot()

def update_plot(self):
if len(self.pressures) == 0:
self.start_time = time.mktime(datetime.now().timetuple())
self.timestamps.append(time.mktime(datetime.now().timetuple()) - self.start_time)
self.pressures.append(self.bar)
#self.generate_plot(
return

if self.bar != self.pressures[-1]:
self.timestamps.append(time.mktime(datetime.now().timetuple()) - self.start_time)
self.pressures.append(self.bar)
#self.generate_plot()

if __name__ == '__main__':
plotter = TankPlotter()
plt.ion()
figure = plt.figure('Fuel tank plotter')
ax = figure.add_subplot(111)
line_pressure, = ax.plot([0], [0])
line_prediction, = ax.plot([0], [0], '--')
ax.set_xlim((0, 30))
ax.set_ylim((0, 300))
ax.set_title("time [min] to 0 bar: Wait for more data",fontsize = 20)
ax.set_xlabel("t [min]", fontsize = 20)
ax.set_ylabel("pressure [bar]", fontsize = 20)
ax.grid()
figure.canvas.draw()
figure.canvas.flush_events()

while True:
if len(plotter.pressures) > 0:
line_pressure.set_xdata(np.array(plotter.timestamps) / 60)
line_pressure.set_ydata(plotter.pressures)
ax.set_xlim((0, plotter.timestamps[-1] / 60. + 30))
ax.set_ylim((0, 300))

if len(plotter.pressures) >= 5:
# Determine slope
dt = plotter.timestamps[-1] - plotter.timestamps[-5]
d_pressure = plotter.pressures[-1] - plotter.pressures[-5]

# Determine point
p_pressure = (plotter.pressures[-5] + plotter.pressures[-4] + plotter.pressures[-3] + plotter.pressures[-2] + plotter.pressures[-1]) / 5.
p_t = (plotter.timestamps[-5] + plotter.timestamps[-4] + plotter.timestamps[-3] + plotter.timestamps[-2] + plotter.timestamps[-1]) / 5.

# Calculate 0 point
dt0 = p_pressure / (-d_pressure/ dt)
t_pressure0 = dt0 + p_t

# Plot line
line_prediction.set_xdata(np.array([p_t, t_pressure0]) / 60.)
line_prediction.set_ydata([p_pressure, 0])
ax.set_xlim([0, t_pressure0 /60. + 30])
ax.set_title("time [min] to 0 bar: " + str(dt0 / 60.), fontsize = 20)

plt.draw()

figure.canvas.draw()
figure.canvas.flush_events()
sys.stdout.flush()
time.sleep(1)
183 changes: 183 additions & 0 deletions sw/ground_segment/python/moving_base/moving_base.py
@@ -0,0 +1,183 @@
#!/usr/bin/env python3
#
# Copyright (C) 2017 Hector Garcia de Marina <hgdemarina@gmail.com>
# 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, see
# <http://www.gnu.org/licenses/>.
#

'''
Moving base simulator
'''

# too many things here
from __future__ import print_function
import sys
import numpy as np
import json
from time import sleep
from os import path, getenv
import time
import pymap3d as pm
import math as m

PPRZ_HOME = getenv("PAPARAZZI_HOME", path.normpath(path.join(path.dirname(path.abspath(__file__)), '../../../../')))
sys.path.append(PPRZ_HOME + "/var/lib/python/")
from pprzlink.ivy import IvyMessagesInterface
from pprzlink.message import PprzMessage

from scipy import linalg as la

class UAV:
def __init__(self, ac_id):
self.initialized = False
self.id = ac_id
self.lat = 0
self.lon = 0
self.alt = 0
self.timeout = 0

class Base:
def __init__(self, freq=10., use_ground_ref=False, ignore_geo_fence=False, verbose=False):
self.step = 1. / freq
self.use_ground_ref = use_ground_ref
self.enabled = True # run sim by default
self.verbose = verbose
self.ids = [5]
self.uavs = [UAV(i) for i in self.ids]
self.time = time.mktime(time.gmtime())
self.speed = 1 # m/s
self.course = -90 # deg
self.heading = -90 # deg
self.lat = 38.08000040764657 #deg
self.lon = -9.1 #deg
self.altitude = 2.0 # starts from 1 m high

# Start IVY interface
self._interface = IvyMessagesInterface("Moving Base")

# bind to GPS_INT message
def ins_cb(ac_id, msg):
if ac_id in self.ids and msg.name == "GPS_INT":
uav = self.uavs[self.ids.index(ac_id)]
i2p = 1. / 2**8 # integer to position
i2v = 1. / 2**19 # integer to velocity
uav.lat[0] = float(msg['lat']) / 1e7
uav.lon[1] = float(msg['lon']) / 1e7
uav.alt[2] = float(msg['alt']) / 100
uav.timeout = 0
uav.initialized = True
if not self.use_ground_ref:
self._interface.subscribe(ins_cb, PprzMessage("telemetry", "INS"))

# bind to GROUND_REF message
def ground_ref_cb(ground_id, msg):
ac_id = int(msg['ac_id'])
if ac_id in self.ids:
uav = self.uavs[self.ids.index(ac_id)]
# X and V in NED
uav.X[0] = float(msg['pos'][1])
uav.X[1] = float(msg['pos'][0])
uav.X[2] = -float(msg['pos'][2])
uav.V[0] = float(msg['speed'][1])
uav.V[1] = float(msg['speed'][0])
uav.V[2] = -float(msg['speed'][2])
uav.timeout = 0
uav.initialized = True
if self.use_ground_ref:
self._interface.subscribe(ground_ref_cb, PprzMessage("ground", "GROUND_REF"))

def __del__(self):
self.stop()

def stop(self):
# Stop IVY interface
if self._interface is not None:
self._interface.shutdown()

def move_base(self, north, east):

out = pm.ned2geodetic(north, east, 0, self.lat, self.lon, self.altitude)
self.lat = out[0]
self.lon = out[1]

def send_pos(self):
'''
Send position of base sation
'''
ready = True
for uav in self.uavs:
if not uav.initialized:
if self.verbose:
print("Waiting for state of rotorcraft ", uav.id)
sys.stdout.flush()
ready = False
if uav.timeout > 0.5:
if self.verbose:
print("The state msg of rotorcraft ", uav.id, " stopped")
sys.stdout.flush()
ready = False

# if not ready:
# return


if self.verbose:
print("Error distances: " + str(E).replace('[','').replace(']',''))
sys.stdout.flush()

for ac in self.uavs:
msg = PprzMessage("datalink", "TARGET_POS")
msg['ac_id'] = ac.id
msg['target_id'] = ac.id
msg['lat'] = int(self.lat * 1e7)
msg['lon'] = int(self.lon * 1e7)
msg['alt'] = int(self.altitude *1000)
msg['speed'] = self.speed
msg['climb'] = 0
msg['course'] = self.course
msg['heading'] = self.heading
self._interface.send(msg)

def run(self):
try:
# The main loop
while True:
# TODO: make better frequency managing
sleep(self.step)

for uav in self.uavs:
uav.timeout = uav.timeout + self.step

# Send base position
if self.enabled:
dn = self.speed*m.cos(self.course/180.0*m.pi)
de = self.speed*m.sin(self.course/180.0*m.pi)
self.move_base(self.step*dn,self.step*de)
self.send_pos()

except KeyboardInterrupt:
self.stop()


if __name__ == '__main__':
import argparse


base = Base()
base.run()

12 changes: 6 additions & 6 deletions sw/supervision/python/configuration_panel.py
Expand Up @@ -5,10 +5,11 @@
from generated.ui_configuration_panel import Ui_ConfigurationPanel
from program_widget import ProgramWidget, TabProgramsState
from conf import *
from programs_conf import parse_tools
from programs_conf import Tool
import subprocess



class ConfigurationPanel(QWidget, Ui_ConfigurationPanel):

clear_error = QtCore.pyqtSignal()
Expand Down Expand Up @@ -67,13 +68,12 @@ def handle_conf_changed(self):
self.currentAC.radio = self.conf_widget.radio.path
self.currentAC.telemetry = self.conf_widget.telemetry.path
self.ac_edited.emit(self.currentAC)

def handle_tools_changed(self, tools: Dict[str, Tool]):
if "Flight Plan Editor" in tools:
self.flight_plan_editor = tools["Flight Plan Editor"]

def edit_flightplan_gcs(self, path):
if self.flight_plan_editor is None:
tools = parse_tools()
if "Flight Plan Editor" in tools:
self.flight_plan_editor = tools["Flight Plan Editor"]

if self.flight_plan_editor is not None:
cmd = [os.path.join(utils.PAPARAZZI_SRC, self.flight_plan_editor.command)]
for arg in self.flight_plan_editor.args:
Expand Down
2 changes: 2 additions & 0 deletions sw/supervision/python/paparazzicenter.py
Expand Up @@ -61,6 +61,8 @@ def __init__(self, parent=None):
self.operation_panel.session.program_spawned.connect(self.header.disable_sets)
self.operation_panel.session.programs_all_stopped.connect(self.header.enable_sets)

self.operation_panel.session.tools_changed.connect(self.configuration_panel.handle_tools_changed)

self.configuration_panel.splitter.splitterMoved.connect(self.update_left_pane_width)
settings = utils.get_settings()
window_size = settings.value("ui/window_size", QtCore.QSize(1000, 600), QtCore.QSize)
Expand Down
2 changes: 2 additions & 0 deletions sw/supervision/python/session_widget.py
Expand Up @@ -22,6 +22,7 @@ class SessionWidget(QWidget, Ui_Session):
programs_all_stopped = QtCore.pyqtSignal()
program_spawned = QtCore.pyqtSignal()
program_state_changed = QtCore.pyqtSignal(TabProgramsState)
tools_changed = QtCore.pyqtSignal(dict) # Dict[str, Tool]

def __init__(self, parent=None):
QWidget.__init__(self, parent=parent)
Expand Down Expand Up @@ -63,6 +64,7 @@ def on_control_panel_changed(self):
current_cp = self.control_panel_combo.currentText()
self.sessions = parse_sessions(current_cp)
self.tools = parse_tools(current_cp)
self.tools_changed.emit(self.tools)
self.init_tools_menu()
sessions_names = [session.name for session in self.sessions]
self.sessions_combo.clear()
Expand Down
1 change: 1 addition & 0 deletions sw/tools/install.py
Expand Up @@ -83,6 +83,7 @@ def cmd_vlc(self):
self.execute('sudo -E apt-get -f -y install ffmpeg vlc jstest-gtk default-jre')
self.execute('sudo -E apt-get install -y python3-pip')
self.execute('python3 -m pip install pyquaternion ivy-python') # Required for NatNat
self.execute('python3 -m pip install pymap3d') # Required for Moving-Base

def cmd_doc(self):
self.view('https://paparazzi-uav.readthedocs.io')
Expand Down