5 changes: 5 additions & 0 deletions sw/airborne/modules/computer_vision/textons.h
Expand Up @@ -39,7 +39,9 @@
extern float *texton_distribution; // main outcome of the image processing: the distribution of textons in the image

// settings
extern uint8_t running;
extern uint8_t load_dictionary;
extern uint8_t reinitialize_dictionary;
extern uint8_t alpha_uint;
extern uint8_t n_textons;
extern uint8_t patch_size; // Should be even
Expand Down Expand Up @@ -67,4 +69,7 @@ void load_texton_dictionary(void);
extern void textons_init(void);
extern void textons_stop(void);

// helper functions (potentially should go elsewhere):
float get_entropy(float *p_dist, int D);

#endif /* TEXTONS_H */
62 changes: 61 additions & 1 deletion sw/airborne/modules/ctrl/approach_moving_target.c
Expand Up @@ -27,11 +27,29 @@

#include "generated/modules.h"
#include "modules/core/abi.h"
#include "filters/low_pass_filter.h"


#ifndef AMT_ERR_SLOWDOWN_GAIN
#define AMT_ERR_SLOWDOWN_GAIN 0.25
#endif

// Filter value in Hz
#ifndef CUTOFF_FREQ_FILTERS_HZ
#define CUTOFF_FREQ_FILTERS_HZ 0.5
#endif


float amt_err_slowdown_gain = AMT_ERR_SLOWDOWN_GAIN;

float approach_moving_target_angle_deg;

float cutoff_freq_filters_hz = CUTOFF_FREQ_FILTERS_HZ

Butterworth2LowPass target_pos_filt[3];
Butterworth2LowPass target_vel_filt[3];
Butterworth2LowPass target_heading_filt;

#define DEBUG_AMT TRUE
#include <stdio.h>

Expand Down Expand Up @@ -75,11 +93,25 @@ static void send_approach_moving_target(struct transport_tx *trans, struct link_

void approach_moving_target_init(void)
{
approach_moving_target_set_low_pass_freq(cutoff_freq_filters_hz);
#if PERIODIC_TELEMETRY
register_periodic_telemetry(DefaultPeriodic, PPRZ_MSG_ID_APPROACH_MOVING_TARGET, send_approach_moving_target);
#endif
}

void approach_moving_target_set_low_pass_freq(float filter_freq) {
cutoff_freq_filters_hz = filter_freq;
// tau = 1/(2*pi*Fc)
float tau = 1.0 / (2.0 * M_PI * cutoff_freq_filters_hz);
float sample_time = 1.0 / PERIODIC_FREQUENCY;
for (uint8_t i = 0; i < 3; i++) {
init_butterworth_2_low_pass(&target_pos_filt[i], tau, sample_time, 0.0);
init_butterworth_2_low_pass(&target_vel_filt[i], tau, sample_time, 0.0);
}
init_butterworth_2_low_pass(&target_heading_filt, tau, sample_time, 0.0);

}

// Update a waypoint such that you can see on the GCS where the drone wants to go
void update_waypoint(uint8_t wp_id, struct FloatVect3 * target_ned) {

Expand Down Expand Up @@ -127,11 +159,39 @@ void follow_diagonal_approach(void) {
return;
}
target_get_vel(&target_vel);

// filter target values
update_butterworth_2_low_pass(&target_pos_filt[0], target_pos.x);
update_butterworth_2_low_pass(&target_pos_filt[1], target_pos.y);
update_butterworth_2_low_pass(&target_pos_filt[2], target_pos.z);

update_butterworth_2_low_pass(&target_vel_filt[0], target_vel.x);
update_butterworth_2_low_pass(&target_vel_filt[1], target_vel.y);
update_butterworth_2_low_pass(&target_vel_filt[2], target_vel.z);

// just filtering the heading will go wrong if it wraps, instead do:
// take the diff with current heading_filtered, wrap that, add to current, insert in filter and wrap again?
// example: target_heading - target_heading_filt (170 - -170 = 340), -> -20 -> -190 -> into filter -> wrap
float heading_diff = target_heading - target_heading_filt.o[0];
FLOAT_ANGLE_NORMALIZE(heading_diff);
float target_heading_input = target_heading_filt.o[0] + heading_diff;
update_butterworth_2_low_pass(&target_heading_filt, target_heading_input);
float target_heading_filt_wrapped = target_heading_filt.o[0];
FLOAT_ANGLE_NORMALIZE(target_heading_filt_wrapped);

target_pos.x = target_pos_filt[0].o[0];
target_pos.y = target_pos_filt[1].o[0];
target_pos.z = target_pos_filt[2].o[0];

target_vel.x = target_vel_filt[0].o[0];
target_vel.y = target_vel_filt[1].o[0];
target_vel.z = target_vel_filt[2].o[0];

VECT3_SMUL(target_vel, target_vel, amt.speed_gain);

// Reference model
float gamma_ref = RadOfDeg(amt.slope_ref);
float psi_ref = RadOfDeg(target_heading + amt.psi_ref);
float psi_ref = RadOfDeg(target_heading_filt_wrapped + amt.psi_ref);

amt.rel_unit_vec.x = cosf(gamma_ref) * cosf(psi_ref);
amt.rel_unit_vec.y = cosf(gamma_ref) * sinf(psi_ref);
Expand Down
2 changes: 2 additions & 0 deletions sw/airborne/modules/ctrl/approach_moving_target.h
Expand Up @@ -47,9 +47,11 @@ struct Amt {

extern struct Amt amt;
extern float amt_err_slowdown_gain;
extern float cutoff_freq_filters_hz;

extern void approach_moving_target_init(void);
extern void follow_diagonal_approach(void);
extern void approach_moving_target_enable(uint8_t wp_id);
extern void approach_moving_target_set_low_pass_freq(float filter_freq);

#endif // APPROACH_MOVING_TARGET_H
4 changes: 2 additions & 2 deletions sw/airborne/modules/ctrl/follow_me.c
Expand Up @@ -130,8 +130,8 @@ void follow_me_periodic(void)
// Rotate the waypoint
float cos_heading = cosf(diff_targetpos_heading/180.*M_PI);
float sin_heading = sinf(diff_targetpos_heading/180.*M_PI);
wp_new_enu.x = ((wp_new_enu.x - cur_targetpos.x) * cos_heading) + ((wp_new_enu.y - cur_targetpos.y) * sin_heading) + + cur_targetpos.x;
wp_new_enu.y = (-(wp_new_enu.y - cur_targetpos.y) * sin_heading) + ((wp_new_enu.y - cur_targetpos.y) * cos_heading) + cur_targetpos.y;
wp_new_enu.x = ((wp_new_enu.x - cur_targetpos.x) * cos_heading) + ((wp_new_enu.y - cur_targetpos.y) * sin_heading) + cur_targetpos.x;
wp_new_enu.y = (-(wp_new_enu.x - cur_targetpos.x) * sin_heading) + ((wp_new_enu.y - cur_targetpos.y) * cos_heading) + cur_targetpos.y;

// Update the waypoint
waypoint_set_enu(wp_id, &wp_new_enu);
Expand Down
4 changes: 0 additions & 4 deletions sw/airborne/modules/ctrl/follow_me.h
Expand Up @@ -40,10 +40,6 @@ extern float follow_me_height;
*/
extern float follow_me_heading;

/** minimum speed in m/s which the ground needs to have in order to update the heading
*/
extern float follow_me_min_speed;

/** Follow me course sin/cos filter value (higher is harder filter)
*/
extern float follow_me_filt;
Expand Down
913 changes: 898 additions & 15 deletions sw/airborne/modules/ctrl/optical_flow_landing.c

Large diffs are not rendered by default.

48 changes: 44 additions & 4 deletions sw/airborne/modules/ctrl/optical_flow_landing.h
Expand Up @@ -46,10 +46,13 @@

#include "std.h"

// maximum number of samples to learn from for SSL:
#define MAX_SAMPLES_LEARNING 25000

struct OpticalFlowLanding {
float agl; ///< agl = height from sonar (only used when using "fake" divergence)
float agl_lp; ///< low-pass version of agl
float lp_const; ///< low-pass filter constant
float lp_const; ///< low-pass value for divergence
float vel; ///< vertical velocity as determined with sonar (only used when using "fake" divergence)
float divergence_setpoint; ///< setpoint for constant divergence approach
float pgain; ///< P-gain for constant divergence control (from divergence error to thrust)
Expand All @@ -61,7 +64,7 @@ struct OpticalFlowLanding {
float d_err; ///< difference of error for the D-gain
float nominal_thrust; ///< nominal thrust around which the PID-control operates
uint32_t VISION_METHOD; ///< whether to use vision (1) or Optitrack / sonar (0)
uint32_t CONTROL_METHOD; ///< type of divergence control: 0 = fixed gain, 1 = adaptive gain
uint32_t CONTROL_METHOD; ///< type of divergence control: 0 = fixed gain, 1 = adaptive gain, 2 = exponential time landing control. 3 = learning control
float cov_set_point; ///< for adaptive gain control, setpoint of the covariance (oscillations)
float cov_limit; ///< for fixed gain control, what is the cov limit triggering the landing
float pgain_adaptive; ///< P-gain for adaptive gain control
Expand All @@ -75,20 +78,57 @@ struct OpticalFlowLanding {
float t_transition; ///< how many seconds the drone has to be oscillating in order to transition to the hover phase with reduced gain
float p_land_threshold; ///< if during the exponential landing the gain reaches this value, the final landing procedure is triggered
bool elc_oscillate; ///< Whether or not to oscillate at beginning of elc to find optimum gain
// Added features for SSL:
volatile bool
learn_gains; ///< set to true if the robot needs to learn a mapping from texton distributions to the p-gain
bool load_weights; ///< load the weights that were learned before
float close_to_edge; ///< if abs(cov_div - reference cov_div) < close_to_edge, then texton distributions and gains are stored for learning
bool use_bias; ///< if true, a bias 1.0f will be added to the feature vector (texton distribution) - this typically leads to very large weights
bool snapshot; ///< if true, besides storing a texton distribution, an image will also be stored (when unstable)
float lp_factor_prediction; ///< low-pass value for the predicted P-value
float ramp_duration; ///< ramp duration in seconds for when the divergence setpoint changes
float pgain_horizontal_factor;///< factor multiplied with the vertical P-gain for horizontal ventral-flow-based control
float igain_horizontal_factor;///< factor multiplied with the vertical I-gain for horizontal ventral-flow-based control
float roll_trim; ///< Roll trim angle in degrees
float pitch_trim; ///< Pitch trim angle in degrees
float omega_LR; ///< Set point for the left-right ventral flow
float omega_FB; ///< Set point for the front-back ventral flow
uint32_t active_motion; ///< Method for actively inducing motion
};

extern struct OpticalFlowLanding of_landing_ctrl;


// Without optitrack set to: GUIDANCE_H_MODE_ATTITUDE
// With optitrack set to: GUIDANCE_H_MODE_HOVER / NAV
#define GUIDANCE_H_MODE_MODULE_SETTING GUIDANCE_H_MODE_NAV
// With optitrack set to: GUIDANCE_H_MODE_HOVER / NAV (NAV is the common option in the experiments.)

#define GUIDANCE_H_MODE_MODULE_SETTING GUIDANCE_H_MODE_MODULE

// Own guidance_v
#define GUIDANCE_V_MODE_MODULE_SETTING GUIDANCE_V_MODE_MODULE

// Implement own horizontal loop:
extern void guidance_h_module_init(void);
extern void guidance_h_module_enter(void);
extern void guidance_h_module_run(bool in_flight);
extern void guidance_h_module_read_rc(void);

// Implement own Vertical loops
extern void guidance_v_module_init(void);
extern void guidance_v_module_enter(void);
extern void guidance_v_module_run(bool in_flight);

// SSL functions:
void save_texton_distribution(void);
void load_texton_distribution(void);
void fit_linear_model_OF(float *targets, float **samples, uint8_t D, uint16_t count, float *parameters,
float *fit_error);
void learn_from_file(void);
float predict_gain(float *distribution);
void save_weights(void);
void load_weights(void);
void recursive_least_squares_batch(float *targets, float **samples, uint8_t D, uint16_t count, float *params,
float *fit_error);
void recursive_least_squares(float target, float *sample, uint8_t length_sample, float *params);

#endif /* OPTICAL_FLOW_LANDING_H_ */
11 changes: 11 additions & 0 deletions sw/airborne/modules/energy/electrical.c
Expand Up @@ -103,6 +103,8 @@ void electrical_init(void)
electrical.current = 0.f;
electrical.charge = 0.f;
electrical.energy = 0.f;
electrical.avg_power = 0;
electrical.avg_cnt = 0;

electrical.bat_low = false;
electrical.bat_critical = false;
Expand Down Expand Up @@ -212,4 +214,13 @@ void electrical_periodic(void)
}
}

float power = electrical.vsupply * electrical.current;
electrical.avg_power += power;
electrical.avg_cnt++;
}

void electrical_avg_reset(float var __attribute__((unused)))
{
electrical.avg_power = 0;
electrical.avg_cnt = 0;
}
16 changes: 10 additions & 6 deletions sw/airborne/modules/energy/electrical.h
Expand Up @@ -42,17 +42,21 @@
#endif

struct Electrical {
float vsupply; ///< supply voltage in V
float current; ///< current in A
float charge; ///< consumed electric charge in Ah
float energy; ///< consumed energy in Wh
bool bat_low; ///< battery low status
bool bat_critical; ///< battery critical status
float vsupply; ///< supply voltage in V
float current; ///< current in A
float charge; ///< consumed electric charge in Ah
float energy; ///< consumed energy in Wh
bool bat_low; ///< battery low status
bool bat_critical; ///< battery critical status

uint32_t avg_power; ///< average power sum
uint32_t avg_cnt; ///< average power counter
};

extern struct Electrical electrical;

extern void electrical_init(void);
extern void electrical_periodic(void);
extern void electrical_avg_reset(float var);

#endif /* ELECTRICAL_H */
7 changes: 6 additions & 1 deletion sw/airborne/modules/esc32/esc32.c
Expand Up @@ -222,14 +222,19 @@ static void parse_esc32(struct esc32_private *esc, uint8_t c) {
}

static void esc32_msg_send(struct transport_tx *trans, struct link_device *dev) {
float temp = 0;
uint8_t id0 = 0;

pprz_msg_send_ESC(trans, dev, AC_ID,
&esc32.params.amps,
&esc32.params.volts_bat,
&esc32.power,
&esc32.params.rpm,
&esc32.params.volts_motor,
&esc32.energy,
0); // only one motor handled for now
&temp,
&id0,
&id0); // only one motor handled for now
}

void esc32_init(void) {
Expand Down
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 */
61 changes: 61 additions & 0 deletions sw/airborne/modules/nav/ballistic_touchdown.c
@@ -0,0 +1,61 @@
/*
* Copyright (C) 2023 Ewoud Smeur
*
* 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 "state.h"

float g = -9.81f;

struct FloatVect2 ballistic_pos;

void ballistic_touchdown_init(void) {
// nothing to be done here
}

/**
* Function that predicts the ballistic crash location
*
* Uses ENU coordinates (vertical up!)
**/
void ballistic_touchdown_run(void) {

struct EnuCoor_f * v = stateGetSpeedEnu_f();
float vz = v->z;

struct FloatVect2 vh;
VECT2_ASSIGN(vh, v->x, v->y);

float h = fabsf(stateGetPositionEnu_f()->z); // Should be height above ground, make sure to initialize local frame on ground

// With h always larger than 0, the sqrt can never give nan
float time_fall = (-vz - sqrtf(vz*vz -2.f*h*g))/g;

struct FloatVect2 crash_offset;

VECT2_SMUL(crash_offset, vh, time_fall);

struct FloatVect2 pos;
pos.x = stateGetPositionEnu_f()->x;
pos.y = stateGetPositionEnu_f()->y;

// The predicted crash position is the current drone position + fall distance
VECT2_SUM(ballistic_pos, pos, crash_offset);
}
26 changes: 26 additions & 0 deletions sw/airborne/modules/nav/ballistic_touchdown.h
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2023 Ewoud Smeur
*
* 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.
*
*/

void ballistic_touchdown_init(void);
void ballistic_touchdown_run(void);

extern struct FloatVect2 ballistic_pos;
70 changes: 70 additions & 0 deletions sw/airborne/modules/sensors/generic_uart.c
@@ -0,0 +1,70 @@
/*
* 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

/* Default max sending message */
#ifndef GENERIC_UART_MAX_SENDLEN
#define GENERIC_UART_MAX_SENDLEN 64
#endif

/* Default max buffer size */
#ifndef GENERIC_UART_MAX_BUFSIZE
#define GENERIC_UART_MAX_BUFSIZE 128
#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[GENERIC_UART_MAX_BUFSIZE];
static uint8_t gen_msg_cnt = 0;

// Look for data on serial port and save it in the buffer
while (gen_msg_cnt < GENERIC_UART_MAX_BUFSIZE && 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 > GENERIC_UART_MAX_SENDLEN) {
DOWNLINK_SEND_PAYLOAD(DefaultChannel, DefaultDevice, gen_msg_cnt, gen_msg_buf);

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
9 changes: 8 additions & 1 deletion sw/tools/install.py
Expand Up @@ -65,13 +65,17 @@ def cmd_jsb(self):

def cmd_gcs(self):
self.execute('sudo -E apt-get -f -y install pprzgcs')
self.execute('sudo -E apt-get -f -y install python3-pyqt5.qtwebkit') # for the documentation

def cmd_mcu(self):
self.execute('sudo -E apt-get -f -y install dfu-util')
self.execute('sudo -E cp conf/system/udev/rules/*.rules /etc/udev/rules.d/ && sudo -E udevadm control --reload-rules')

def cmd_gazebo(self):
self.execute('sudo -E apt-get -f -y install gazebo9 libgazebo9-dev')
if float(release['RELEASE']) > 20.04:
self.execute('sudo -E apt-get -f -y install gazebo libgazebo-dev')
else:
self.execute('sudo -E apt-get -f -y install gazebo9 libgazebo9-dev')
self.execute('git submodule init && git submodule sync && git submodule update ./sw/ext/tudelft_gazebo_models')


Expand All @@ -83,6 +87,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 Expand Up @@ -157,6 +162,8 @@ def __init__(self):

button8 = QPushButton('8) Bebop Opencv')
button8.clicked.connect(self.cmd_bebopcv)
if float(release['RELEASE']) > 20.04:
button8.setDisables(True)
btn_layout.addWidget(button8)

button9 = QPushButton('9) VLC + Joystick + Natnet')
Expand Down