mirror of
https://github.com/paparazzi/paparazzi.git
synced 2026-09-28 16:12:40 +08:00
Airspeed consistency check with onboard wind estimation (#3478)
Issues due date / Add labels to issues (push) Has been cancelled
Doxygen / build (push) Has been cancelled
Issues due date / Add labels to issues (push) Has been cancelled
Doxygen / build (push) Has been cancelled
* initial commit airspeed consistency check module * small edit * address comments * use INFO_MSG for airspeed consistency module
This commit is contained in:
@@ -143,6 +143,7 @@
|
||||
<module name="rotwing_state"/>
|
||||
<module name="preflight_checks"/>
|
||||
<module name="pfc_actuators"/>
|
||||
<module name="airspeed_consistency"/>
|
||||
<module name="agl_dist"/>
|
||||
<module name="approach_moving_target"/>
|
||||
</firmware>
|
||||
|
||||
@@ -136,6 +136,7 @@
|
||||
<define name="SDLOG_PREFLIGHT_ERROR" value="TRUE"/>
|
||||
</module>
|
||||
<module name="pfc_actuators"/>
|
||||
<module name="airspeed_consistency"/>
|
||||
<module name="agl_dist"/>
|
||||
<module name="approach_moving_target"/>
|
||||
</firmware>
|
||||
|
||||
@@ -136,6 +136,7 @@
|
||||
<define name="SDLOG_PREFLIGHT_ERROR" value="TRUE"/>
|
||||
</module>
|
||||
<module name="pfc_actuators"/>
|
||||
<module name="airspeed_consistency"/>
|
||||
<module name="agl_dist"/>
|
||||
<module name="approach_moving_target"/>
|
||||
</firmware>
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
<define name="SDLOG_PREFLIGHT_ERROR" value="TRUE"/>
|
||||
</module>
|
||||
<module name="pfc_actuators"/>
|
||||
<module name="airspeed_consistency"/>
|
||||
<module name="agl_dist"/>
|
||||
<module name="approach_moving_target"/>
|
||||
</firmware>
|
||||
|
||||
@@ -136,6 +136,7 @@
|
||||
<define name="SDLOG_PREFLIGHT_ERROR" value="TRUE"/>
|
||||
</module>
|
||||
<module name="pfc_actuators"/>
|
||||
<module name="airspeed_consistency"/>
|
||||
<module name="agl_dist"/>
|
||||
<module name="approach_moving_target"/>
|
||||
</firmware>
|
||||
|
||||
@@ -136,6 +136,7 @@
|
||||
<define name="SDLOG_PREFLIGHT_ERROR" value="TRUE"/>
|
||||
</module>
|
||||
<module name="pfc_actuators"/>
|
||||
<module name="airspeed_consistency"/>
|
||||
<module name="agl_dist"/>
|
||||
<module name="approach_moving_target"/>
|
||||
</firmware>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE module SYSTEM "module.dtd">
|
||||
|
||||
<module name="airspeed_consistency" dir="checks">
|
||||
<doc>
|
||||
<description>
|
||||
Check consistency of airspeed measurements while flying circles.
|
||||
</description>
|
||||
</doc>
|
||||
<settings>
|
||||
<dl_settings>
|
||||
<dl_settings NAME="Airspeed Consistency">
|
||||
<dl_setting var="asc_reset" min="0" max="1" step="1" values="FALSE|TRUE" type="bool" module="modules/checks/airspeed_consistency" handler="reset"/>
|
||||
</dl_settings>
|
||||
</dl_settings>
|
||||
</settings>
|
||||
<dep>
|
||||
<depends></depends>
|
||||
</dep>
|
||||
<header>
|
||||
<file name="airspeed_consistency.h"/>
|
||||
</header>
|
||||
<init fun="airspeed_consistency_init()"/>
|
||||
<periodic fun="airspeed_consistency_periodic()" freq="1"/>
|
||||
<makefile>
|
||||
<file name="airspeed_consistency.c"/>
|
||||
<file name="pprz_circfit_float.c" dir="math"/>
|
||||
</makefile>
|
||||
</module>
|
||||
@@ -601,7 +601,7 @@
|
||||
telemetry="telemetry/highspeed_rotorcraft.xml"
|
||||
flight_plan="flight_plans/tudelft/rotwing_EHVB.xml"
|
||||
settings="settings/rotorcraft_basic.xml"
|
||||
settings_modules="modules/air_data.xml modules/airspeed_ms45xx_i2c.xml modules/airspeed_uavcan.xml modules/approach_moving_target.xml modules/eff_scheduling_rotwing.xml modules/ekf_aw.xml modules/electrical.xml modules/follow_me.xml modules/gps.xml modules/gps_ublox.xml modules/guidance_indi_hybrid.xml modules/guidance_rotorcraft.xml modules/imu_common.xml modules/imu_heater.xml modules/ins_ekf2.xml modules/logger_sd_chibios.xml modules/nav_hybrid.xml modules/nav_rotorcraft.xml modules/parachute.xml modules/pfc_actuators.xml modules/preflight_checks.xml modules/rotwing_state.xml modules/stabilization_indi.xml modules/sys_id_auto_doublets.xml modules/sys_id_doublet.xml modules/target_pos.xml"
|
||||
settings_modules="modules/air_data.xml modules/airspeed_consistency.xml modules/airspeed_ms45xx_i2c.xml modules/airspeed_uavcan.xml modules/approach_moving_target.xml modules/eff_scheduling_rotwing.xml modules/ekf_aw.xml modules/electrical.xml modules/follow_me.xml modules/gps.xml modules/gps_ublox.xml modules/guidance_indi_hybrid.xml modules/guidance_rotorcraft.xml modules/imu_common.xml modules/imu_heater.xml modules/ins_ekf2.xml modules/logger_sd_chibios.xml modules/nav_hybrid.xml modules/nav_rotorcraft.xml modules/parachute.xml modules/pfc_actuators.xml modules/preflight_checks.xml modules/rotwing_state.xml modules/stabilization_indi.xml modules/sys_id_auto_doublets.xml modules/sys_id_doublet.xml modules/target_pos.xml"
|
||||
gui_color="red"
|
||||
/>
|
||||
<aircraft
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "math/pprz_circfit_float.h"
|
||||
#include <math.h>
|
||||
|
||||
#ifndef PPRZ_CIRCFIT_EPSILON
|
||||
#define PPRZ_CIRCFIT_EPSILON 1e-3f
|
||||
#endif
|
||||
|
||||
#ifndef PPRZ_CIRCFIT_ITER_MAX
|
||||
#define PPRZ_CIRCFIT_ITER_MAX 250
|
||||
#endif
|
||||
|
||||
enum CircFitStatus_t pprz_circfit_wei_float(struct circle_t *c, const float *x, const float *y, uint16_t n, struct circle_t *g) {
|
||||
|
||||
// Check if initial guess is provided
|
||||
if (g != NULL) {
|
||||
c->r = g->r;
|
||||
c->x = g->x;
|
||||
c->y = g->y;
|
||||
} else {
|
||||
c->x = 0.0f;
|
||||
c->y = 0.0f;
|
||||
c->r = 1.0f;
|
||||
}
|
||||
|
||||
float norm[n];
|
||||
float x_prev = 0;
|
||||
float y_prev = 0;
|
||||
float r_prev = -1;
|
||||
uint16_t i = 0;
|
||||
|
||||
while (fabsf(c->r - r_prev) > PPRZ_CIRCFIT_EPSILON || fabsf(c->x - x_prev) > PPRZ_CIRCFIT_EPSILON || fabsf(c->y - y_prev) > PPRZ_CIRCFIT_EPSILON) {
|
||||
|
||||
float sum_norm = 0.0f;
|
||||
x_prev = c->x;
|
||||
y_prev = c->y;
|
||||
r_prev = c->r;
|
||||
|
||||
// Prepare ||x - c||
|
||||
for (int i = 0; i < n; i++) {
|
||||
norm[i] = sqrtf((x[i] - x_prev) * (x[i] - x_prev) + (y[i] - y_prev) * (y[i] - y_prev));
|
||||
sum_norm += norm[i];
|
||||
}
|
||||
|
||||
c->r = sum_norm / n;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
c->x += x[i] + c->r * (x_prev - x[i]) / norm[i];
|
||||
c->y += y[i] + c->r * (y_prev - y[i]) / norm[i];
|
||||
}
|
||||
|
||||
c->x /= n;
|
||||
c->y /= n;
|
||||
|
||||
if (i >= PPRZ_CIRCFIT_ITER_MAX) {
|
||||
return CIRC_FIT_ITERATION_LIMIT; // Reached iteration limit
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
}
|
||||
|
||||
return CIRC_FIT_OK; // Circle fit successful
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef PPRZ_CIRCFIT_FLOAT_H
|
||||
#define PPRZ_CIRCFIT_FLOAT_H
|
||||
|
||||
#include <std.h>
|
||||
|
||||
enum CircFitStatus_t {
|
||||
CIRC_FIT_OK = 0, // Circle fit successful
|
||||
CIRC_FIT_ERROR = -1, // Circle fit failed
|
||||
CIRC_FIT_ITERATION_LIMIT = -2 // Circle fit reached iteration limit
|
||||
};
|
||||
|
||||
struct circle_t {
|
||||
float x; // x coordinate of the circle center
|
||||
float y; // y coordinate of the circle center
|
||||
float r; // radius of the circle
|
||||
};
|
||||
|
||||
/*
|
||||
* Fitting Noisy Data to a Circle: A Simple Iterative Maximum Likelihood Approach
|
||||
* Wei Li, et al.
|
||||
* DOI: 10.1109/icc.2011.5963101
|
||||
*
|
||||
* inputs:
|
||||
* c: struct to store the fitted circle
|
||||
* x: x coordinates used during fitting
|
||||
* y: y coordinates used during fitting
|
||||
* n: number of points
|
||||
* g: initial guess for the circle (NULL for no initial guess)
|
||||
*/
|
||||
enum CircFitStatus_t pprz_circfit_wei_float(struct circle_t *c, const float *x, const float *y, uint16_t n, struct circle_t *g);
|
||||
|
||||
|
||||
#endif // PPRZ_CIRCFIT_FLOAT_H
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Noah Wechtler <noahwechtler@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, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file "modules/checks/airspeed_consistency.c"
|
||||
* @author Noah Wechtler <noahwechtler@tudelft.nl>
|
||||
* Simple consistency check for airspeed measurements while flying circles
|
||||
*/
|
||||
|
||||
#include "modules/checks/airspeed_consistency.h"
|
||||
#include "navigation.h"
|
||||
#include "state.h"
|
||||
#include "filters/low_pass_filter.h"
|
||||
#include "generated/modules.h"
|
||||
#include "modules/core/abi.h"
|
||||
#include "modules/datalink/telemetry.h"
|
||||
#include "modules/nav/nav_rotorcraft_base.h"
|
||||
|
||||
#ifndef AIRSPEED_CONSISTENCY_MIN_SAMPLES
|
||||
#define AIRSPEED_CONSISTENCY_MIN_SAMPLES 20 // Minimum number of samples before circle fitting starts
|
||||
#endif
|
||||
|
||||
#ifndef AIRSPEED_CONSISTENCY_INTERVAL
|
||||
#define AIRSPEED_CONSISTENCY_INTERVAL 10
|
||||
#endif
|
||||
|
||||
#if AIRSPEED_CONSISTENCY_BUFFER_SIZE < AIRSPEED_CONSISTENCY_MIN_SAMPLES
|
||||
#error "AIRSPEED_CONSISTENCY_BUFFER_SIZE must be larger than or equal to AIRSPEED_CONSISTENCY_MIN_SAMPLES"
|
||||
#endif
|
||||
|
||||
#if AIRSPEED_CONSISTENCY_BUFFER_SIZE > UINT16_MAX
|
||||
#error "AIRSPEED_CONSISTENCY_BUFFER_SIZE must be less than or equal to UINT16_MAX"
|
||||
#endif
|
||||
|
||||
bool asc_reset = false; // Flag to reset the airspeed consistency check
|
||||
|
||||
static void save_speeds(void);
|
||||
static void reset_speeds(void);
|
||||
static enum CircFitStatus_t get_circle_from_speeds(struct circle_t *c, float *N, float *E);
|
||||
|
||||
static struct asc_t asc = {0};
|
||||
static Butterworth2LowPass tas_filt;
|
||||
|
||||
void airspeed_consistency_init(void) {
|
||||
reset_speeds();
|
||||
float tau = 1.0 / (2.0 * M_PI);
|
||||
init_butterworth_2_low_pass(&tas_filt, tau, 1.0 / NAVIGATION_FREQUENCY, 0.0);
|
||||
}
|
||||
|
||||
void airspeed_consistency_periodic(void) {
|
||||
struct FloatVect2 diff;
|
||||
VECT2_DIFF(diff, nav_rotorcraft_base.circle.center, *stateGetPositionEnu_f());
|
||||
float dist_to_point = float_vect2_norm(&diff);
|
||||
|
||||
if (nav.horizontal_mode != NAV_HORIZONTAL_MODE_CIRCLE || dist_to_point > fabsf(nav_rotorcraft_base.circle.radius) + ARRIVED_AT_WAYPOINT
|
||||
|| dist_to_point < fabsf(nav_rotorcraft_base.circle.radius) - ARRIVED_AT_WAYPOINT) {
|
||||
// Wipe old data if not flying circles
|
||||
if (asc.i != 0) {
|
||||
reset_speeds();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
save_speeds();
|
||||
|
||||
RunOnceEvery(AIRSPEED_CONSISTENCY_PERIODIC_FREQ * AIRSPEED_CONSISTENCY_INTERVAL, {
|
||||
if (asc.filled || asc.i >= AIRSPEED_CONSISTENCY_MIN_SAMPLES) {
|
||||
asc.as_circ_status = get_circle_from_speeds(&asc.as, asc.as_N, asc.as_E);
|
||||
asc.gs_circ_status = get_circle_from_speeds(&asc.gs, asc.gs_N, asc.gs_E);
|
||||
}
|
||||
|
||||
if (asc.as_circ_status == CIRC_FIT_OK && asc.gs_circ_status == CIRC_FIT_OK && asc.as.r > 1e-3) {
|
||||
asc.ratio = asc.gs.r / asc.as.r; // Calculate the ratio of the radii of the circles
|
||||
asc.ratio *= asc.ratio; // Square the ratio so it can be multiplied with the current airspeed sensor scale
|
||||
char error_msg[100];
|
||||
int rc = snprintf(error_msg, sizeof(error_msg), "ASC: ratio squared = %.4f", asc.ratio);
|
||||
DOWNLINK_SEND_INFO_MSG(DefaultChannel, DefaultDevice, rc, error_msg);
|
||||
#if FLIGHTRECORDER_SDLOG
|
||||
pprz_msg_send_INFO_MSG(&pprzlog_tp.trans_tx, &flightrecorder_sdlog.device, AC_ID, rc, error_msg);
|
||||
#endif
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void save_speeds(void) {
|
||||
asc.gs_N[asc.i] = stateGetSpeedNed_f()->x;
|
||||
asc.gs_E[asc.i] = stateGetSpeedNed_f()->y;
|
||||
|
||||
update_butterworth_2_low_pass(&tas_filt, air_data.tas);
|
||||
float psi = stateGetNedToBodyEulers_f()->psi;
|
||||
asc.as_N[asc.i] = tas_filt.o[0] * cosf(psi);
|
||||
asc.as_E[asc.i] = tas_filt.o[0] * sinf(psi);
|
||||
|
||||
asc.i++;
|
||||
if (asc.i >= AIRSPEED_CONSISTENCY_BUFFER_SIZE) {
|
||||
asc.filled = true; // Set to true if the buffer has been filled once
|
||||
asc.i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_speeds(void) {
|
||||
asc.filled = false;
|
||||
asc.i = 0;
|
||||
asc.as_circ_status = CIRC_FIT_ERROR;
|
||||
asc.gs_circ_status = CIRC_FIT_ERROR;
|
||||
|
||||
for (int j = 0; j < AIRSPEED_CONSISTENCY_BUFFER_SIZE; j++) {
|
||||
asc.gs_N[j] = NAN;
|
||||
asc.gs_E[j] = NAN;
|
||||
asc.as_N[j] = NAN;
|
||||
asc.as_E[j] = NAN;
|
||||
}
|
||||
}
|
||||
|
||||
static enum CircFitStatus_t get_circle_from_speeds(struct circle_t *c, float *X, float *Y) {
|
||||
uint16_t N = asc.filled? AIRSPEED_CONSISTENCY_BUFFER_SIZE : asc.i;
|
||||
enum CircFitStatus_t status = pprz_circfit_wei_float(c, X, Y, N, NULL); // Feeding the last estimated circle as initial guess leads to NaNs here
|
||||
return status;
|
||||
}
|
||||
|
||||
void airspeed_consistency_reset(bool __attribute__((unused)) reset) {
|
||||
reset_speeds();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Noah Wechtler <noahwechtler@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, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file "modules/checks/airspeed_consistency.h"
|
||||
* @author Noah Wechtler <noahwechtler@tudelft.nl>
|
||||
* Check the consistency of airspeed measurements while flying circles
|
||||
*/
|
||||
|
||||
#ifndef AIRSPEED_CONSISTENCY_H
|
||||
#define AIRSPEED_CONSISTENCY_H
|
||||
|
||||
#ifndef AIRSPEED_CONSISTENCY_BUFFER_SIZE
|
||||
#define AIRSPEED_CONSISTENCY_BUFFER_SIZE 255
|
||||
#endif
|
||||
|
||||
#include <std.h>
|
||||
#include "math/pprz_circfit_float.h"
|
||||
#include "filters/low_pass_filter.h"
|
||||
|
||||
extern bool asc_reset;
|
||||
|
||||
struct asc_t {
|
||||
bool filled;
|
||||
uint16_t i;
|
||||
float gs_N[AIRSPEED_CONSISTENCY_BUFFER_SIZE];
|
||||
float gs_E[AIRSPEED_CONSISTENCY_BUFFER_SIZE];
|
||||
float as_N[AIRSPEED_CONSISTENCY_BUFFER_SIZE];
|
||||
float as_E[AIRSPEED_CONSISTENCY_BUFFER_SIZE];
|
||||
struct circle_t as;
|
||||
struct circle_t gs;
|
||||
enum CircFitStatus_t as_circ_status;
|
||||
enum CircFitStatus_t gs_circ_status;
|
||||
float ratio;
|
||||
};
|
||||
|
||||
extern void airspeed_consistency_init(void);
|
||||
extern void airspeed_consistency_periodic(void);
|
||||
extern void airspeed_consistency_reset(bool reset); // Reset the airspeed consistency check
|
||||
|
||||
#endif // AIRSPEED_CONSISTENCY_H
|
||||
@@ -22,15 +22,26 @@ import wx
|
||||
import rotwing_viewer
|
||||
|
||||
class RotWingApp(wx.App):
|
||||
|
||||
def __init__(self, args):
|
||||
self.args = args
|
||||
wx.App.__init__(self)
|
||||
|
||||
def OnInit(self):
|
||||
self.main = rotwing_viewer.RotWingFrame()
|
||||
self.main = rotwing_viewer.RotWingFrame(self.args)
|
||||
self.main.Show()
|
||||
self.SetTopWindow(self.main)
|
||||
return True
|
||||
|
||||
def main():
|
||||
application = RotWingApp(0)
|
||||
def main(args):
|
||||
application = RotWingApp(args)
|
||||
application.MainLoop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Rotwing Status Monitor")
|
||||
parser.add_argument('-s', '--speech', dest='speech', default=False, type=bool, help="Rotwing status viewer speech")
|
||||
args = parser.parse_args()
|
||||
|
||||
main(args)
|
||||
@@ -24,7 +24,9 @@ import os
|
||||
import math
|
||||
import datetime
|
||||
import numpy as np
|
||||
|
||||
import pyttsx3
|
||||
import re
|
||||
engine = pyttsx3.init()
|
||||
|
||||
PPRZ_HOME = os.getenv("PAPARAZZI_HOME", os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
'../../../..')))
|
||||
@@ -39,6 +41,18 @@ from pprzlink.ivy import IvyMessagesInterface
|
||||
WIDTH = 600
|
||||
BARH = 140
|
||||
|
||||
class Speech(object):
|
||||
|
||||
def __init__(self):
|
||||
self.last_speech_time = datetime.datetime.now()
|
||||
|
||||
def speak_every_interval(self, text, interval):
|
||||
if (datetime.datetime.now() - self.last_speech_time).total_seconds() > interval:
|
||||
self.last_speech_time = datetime.datetime.now()
|
||||
engine.say(text)
|
||||
engine.runAndWait()
|
||||
|
||||
|
||||
class EscMessage(object):
|
||||
def __init__(self, msg):
|
||||
self.id = int(msg['motor_id'])
|
||||
@@ -173,6 +187,14 @@ class AIRDATAMessage(object):
|
||||
self.airspeed = float(msg['airspeed'])
|
||||
self.tas = float(msg['tas'])
|
||||
|
||||
class INFOMSGMessage(object):
|
||||
def __init__(self, msg):
|
||||
str_of_char_arr = ''.join(msg['msg'])
|
||||
str_of_char_arr = str_of_char_arr.replace('\"', '')
|
||||
str_of_char_arr = str_of_char_arr.split(" ")
|
||||
if str_of_char_arr[0] == 'ASC:':
|
||||
self.ratio = float(str_of_char_arr[-1])
|
||||
|
||||
class IMUHEATERMessage(object):
|
||||
def __init__(self, msg):
|
||||
self.meas_temp = msg['meas_temp']
|
||||
@@ -362,6 +384,10 @@ class RotWingFrame(wx.Frame):
|
||||
self.air_data = AIRDATAMessage(msg)
|
||||
wx.CallAfter(self.update)
|
||||
|
||||
if msg.name == "INFO_MSG":
|
||||
self.infomsg = INFOMSGMessage(msg)
|
||||
wx.CallAfter(self.update)
|
||||
|
||||
if msg.name == "IMU_HEATER":
|
||||
self.imu_heater = IMUHEATERMessage(msg)
|
||||
wx.CallAfter(self.update)
|
||||
@@ -503,35 +529,52 @@ class RotWingFrame(wx.Frame):
|
||||
dc.DrawText("Nav airspeed: " + str(round(self.rw_status.nav_airspeed,1 )) + " [min: " + str(round(self.rw_status.min_airspeed,1 )) + ", max:" + str(round(self.rw_status.max_airspeed,1 )) + "]", 10, int(4.5*line))
|
||||
if hasattr(self, 'air_data'):
|
||||
dc.DrawText("Meas airspeed: " + str(round(self.air_data.airspeed,1 )) + " (TAS: " + str(round(self.air_data.tas,1 )) + ")", 10, int(5.5*line))
|
||||
|
||||
if hasattr(self, 'infomsg') and hasattr(self.infomsg, 'ratio'):
|
||||
# MS45XX inflight calibration monitoring
|
||||
if abs(1-self.infomsg.ratio) < 0.08:
|
||||
dc.SetTextForeground(wx.Colour(0, 0, 0))
|
||||
dc.DrawText("Airspeed ratio: " + str(round(self.infomsg.ratio, 2)), 10, int(6.5*line))
|
||||
elif abs(1-self.infomsg.ratio) < 0.15:
|
||||
dc.SetTextForeground(wx.Colour(139, 64, 0))
|
||||
dc.DrawText("Airspeed ratio: " + str(round(self.infomsg.ratio, 2)), 10, int(6.5*line))
|
||||
if self.speech:
|
||||
self.airspeed_speaker.speak_every_interval("Airspeed is off by more than 8%, Please Calibrate", 30)
|
||||
else:
|
||||
dc.SetTextForeground(wx.Colour(255, 0, 0))
|
||||
dc.DrawText("Airspeed ratio: " + str(round(self.infomsg.ratio, 2)), 10, int(6.5*line))
|
||||
if self.speech:
|
||||
self.airspeed_speaker.speak_every_interval("Airspeed is off by more than 15%, Please Calibrate", 5)
|
||||
|
||||
#self.StatusBox(dc, 5, 5, 0, 0, self.rw_status.get_state(), 1, 1)
|
||||
dc.SetTextForeground(wx.Colour(0, 0, 0))
|
||||
dc.DrawText("Force Skew: ", 10, int(6.5*line))
|
||||
dc.DrawText("Force Skew: ", 10, int(7.5*line))
|
||||
lbw = dc.GetTextExtent("Force Skew: ").width
|
||||
if self.rw_status.skew_forced:
|
||||
dc.SetTextForeground(wx.Colour(255, 0, 0))
|
||||
dc.DrawText("(Enabled)", 10 + lbw, int(6.5*line))
|
||||
dc.DrawText("(Enabled)", 10 + lbw, int(7.5*line))
|
||||
else:
|
||||
dc.SetTextForeground(wx.Colour(0, 0, 0))
|
||||
dc.DrawText("(Disabled)", 10 + lbw, int(6.5*line))
|
||||
dc.DrawText("(Disabled)", 10 + lbw, int(7.5*line))
|
||||
|
||||
if hasattr(self, 'imu_heater'):
|
||||
imu_temp = float(self.imu_heater.meas_temp)
|
||||
if imu_temp < 65.0:
|
||||
dc.SetTextForeground(wx.Colour(0, 0, 0))
|
||||
dc.DrawText("Meas IMU temp: " + str(round(imu_temp, 0)), 10, int(7.5*line))
|
||||
dc.DrawText("Meas IMU temp: " + str(round(imu_temp, 0)), 10, int(8.5*line))
|
||||
elif imu_temp < 85.0:
|
||||
dc.SetTextForeground(wx.Colour(139, 64, 0))
|
||||
dc.DrawText("Meas IMU temp: " + str(round(imu_temp, 0)), 10, int(7.5*line))
|
||||
dc.DrawText("Meas IMU temp: " + str(round(imu_temp, 0)), 10, int(8.5*line))
|
||||
else:
|
||||
dc.SetTextForeground(wx.Colour(255, 0, 0))
|
||||
dc.DrawText("Meas IMU temp: " + str(round(imu_temp, 0)), 10, int(7.5*line))
|
||||
dc.DrawText("Meas IMU temp: " + str(round(imu_temp, 0)), 10, int(8.5*line))
|
||||
|
||||
if self.powers.get_backbat() != None:
|
||||
dc.DrawText("Back Bat: " + str(round(self.powers.get_backbat().current*self.powers.get_backbat().voltage, 1)) + "W (" + str(round(self.powers.get_backbat().voltage, 1)) + "V, " + str(round(self.powers.get_backbat().current, 1)) + "A)", 10, int(8.5*line))
|
||||
dc.DrawText("Back Bat: " + str(round(self.powers.get_backbat().current*self.powers.get_backbat().voltage, 1)) + "W (" + str(round(self.powers.get_backbat().voltage, 1)) + "V, " + str(round(self.powers.get_backbat().current, 1)) + "A)", 10, int(9.5*line))
|
||||
if self.powers.get_frontbat() != None:
|
||||
dc.DrawText("Front Bat: " + str(round(self.powers.get_frontbat().current*self.powers.get_frontbat().voltage, 1)) + "W (" + str(round(self.powers.get_frontbat().voltage, 1)) + "V, " + str(round(self.powers.get_frontbat().current, 1)) + "A)", 10, int(9.5*line))
|
||||
dc.DrawText("Front Bat: " + str(round(self.powers.get_frontbat().current*self.powers.get_frontbat().voltage, 1)) + "W (" + str(round(self.powers.get_frontbat().voltage, 1)) + "V, " + str(round(self.powers.get_frontbat().current, 1)) + "A)", 10, int(10.5*line))
|
||||
if self.powers.get_backmot() != None:
|
||||
dc.DrawText("Back Mot: " + str(round(self.powers.get_backmot().current*self.powers.get_backmot().voltage, 1)) + "W (" + str(round(self.powers.get_backmot().voltage, 1)) + "V, " + str(round(self.powers.get_backmot().current, 1)) + "A)", 10, int(10.5*line))
|
||||
dc.DrawText("Back Mot: " + str(round(self.powers.get_backmot().current*self.powers.get_backmot().voltage, 1)) + "W (" + str(round(self.powers.get_backmot().voltage, 1)) + "V, " + str(round(self.powers.get_backmot().current, 1)) + "A)", 10, int(11.5*line))
|
||||
|
||||
if hasattr(self, 'fuelcell'):
|
||||
dc.SetBrush(wx.Brush(wx.Colour(200,200,200)))
|
||||
@@ -610,7 +653,7 @@ class RotWingFrame(wx.Frame):
|
||||
except:
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, args):
|
||||
|
||||
self.w = WIDTH
|
||||
self.h = WIDTH + BARH
|
||||
@@ -642,7 +685,12 @@ class RotWingFrame(wx.Frame):
|
||||
self.interface = IvyMessagesInterface("rotwingframe")
|
||||
self.interface.subscribe(self.message_recv)
|
||||
|
||||
if args.speech == True:
|
||||
self.speech = True
|
||||
self.airspeed_speaker = Speech()
|
||||
else:
|
||||
self.speech = False
|
||||
|
||||
def OnClose(self, event):
|
||||
self.interface.shutdown()
|
||||
self.Destroy()
|
||||
|
||||
self.Destroy()
|
||||
Reference in New Issue
Block a user