diff --git a/conf/airframes/tudelft/rotwing_v3b.xml b/conf/airframes/tudelft/rotwing_v3b.xml index 02f0b1557a..5ad15a7649 100644 --- a/conf/airframes/tudelft/rotwing_v3b.xml +++ b/conf/airframes/tudelft/rotwing_v3b.xml @@ -143,6 +143,7 @@ + diff --git a/conf/airframes/tudelft/rotwing_v3d.xml b/conf/airframes/tudelft/rotwing_v3d.xml index 196c668502..adeb1f174d 100644 --- a/conf/airframes/tudelft/rotwing_v3d.xml +++ b/conf/airframes/tudelft/rotwing_v3d.xml @@ -136,6 +136,7 @@ + diff --git a/conf/airframes/tudelft/rotwing_v3e.xml b/conf/airframes/tudelft/rotwing_v3e.xml index 32baea627d..9cc143e698 100644 --- a/conf/airframes/tudelft/rotwing_v3e.xml +++ b/conf/airframes/tudelft/rotwing_v3e.xml @@ -136,6 +136,7 @@ + diff --git a/conf/airframes/tudelft/rotwing_v3f.xml b/conf/airframes/tudelft/rotwing_v3f.xml index f6338e89d4..0cedfd1c43 100644 --- a/conf/airframes/tudelft/rotwing_v3f.xml +++ b/conf/airframes/tudelft/rotwing_v3f.xml @@ -134,6 +134,7 @@ + diff --git a/conf/airframes/tudelft/rotwing_v3g.xml b/conf/airframes/tudelft/rotwing_v3g.xml index 7fc5d6a553..cd66a133c7 100644 --- a/conf/airframes/tudelft/rotwing_v3g.xml +++ b/conf/airframes/tudelft/rotwing_v3g.xml @@ -136,6 +136,7 @@ + diff --git a/conf/airframes/tudelft/rotwing_v3h.xml b/conf/airframes/tudelft/rotwing_v3h.xml index 6ce71f2861..e0408c0671 100644 --- a/conf/airframes/tudelft/rotwing_v3h.xml +++ b/conf/airframes/tudelft/rotwing_v3h.xml @@ -136,6 +136,7 @@ + diff --git a/conf/modules/airspeed_consistency.xml b/conf/modules/airspeed_consistency.xml new file mode 100644 index 0000000000..cefc39a8e4 --- /dev/null +++ b/conf/modules/airspeed_consistency.xml @@ -0,0 +1,28 @@ + + + + + + Check consistency of airspeed measurements while flying circles. + + + + + + + + + + + + +
+ +
+ + + + + + +
diff --git a/conf/userconf/tudelft/conf.xml b/conf/userconf/tudelft/conf.xml index c97237c8bd..be57bcf6d3 100644 --- a/conf/userconf/tudelft/conf.xml +++ b/conf/userconf/tudelft/conf.xml @@ -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" /> . + */ + +#include "math/pprz_circfit_float.h" +#include + +#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 + +} \ No newline at end of file diff --git a/sw/airborne/math/pprz_circfit_float.h b/sw/airborne/math/pprz_circfit_float.h new file mode 100644 index 0000000000..f0ddfaff6b --- /dev/null +++ b/sw/airborne/math/pprz_circfit_float.h @@ -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 + * . + */ + +#ifndef PPRZ_CIRCFIT_FLOAT_H +#define PPRZ_CIRCFIT_FLOAT_H + +#include + +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 \ No newline at end of file diff --git a/sw/airborne/modules/checks/airspeed_consistency.c b/sw/airborne/modules/checks/airspeed_consistency.c new file mode 100644 index 0000000000..6b70756314 --- /dev/null +++ b/sw/airborne/modules/checks/airspeed_consistency.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2025 Noah Wechtler + * + * 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 + * . + */ + +/** + * @file "modules/checks/airspeed_consistency.c" + * @author Noah Wechtler + * 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(); +} \ No newline at end of file diff --git a/sw/airborne/modules/checks/airspeed_consistency.h b/sw/airborne/modules/checks/airspeed_consistency.h new file mode 100644 index 0000000000..96a21ae8b7 --- /dev/null +++ b/sw/airborne/modules/checks/airspeed_consistency.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2025 Noah Wechtler + * + * 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 + * . + */ + +/** + * @file "modules/checks/airspeed_consistency.h" + * @author Noah Wechtler + * 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 +#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 \ No newline at end of file diff --git a/sw/ground_segment/python/rotwing_mon/rotwing_mon.py b/sw/ground_segment/python/rotwing_mon/rotwing_mon.py index 64fcc1c84d..324dbd2b9a 100755 --- a/sw/ground_segment/python/rotwing_mon/rotwing_mon.py +++ b/sw/ground_segment/python/rotwing_mon/rotwing_mon.py @@ -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() \ No newline at end of file + 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) \ No newline at end of file diff --git a/sw/ground_segment/python/rotwing_mon/rotwing_viewer.py b/sw/ground_segment/python/rotwing_mon/rotwing_viewer.py index 4a03d5ba13..7dc98527cc 100644 --- a/sw/ground_segment/python/rotwing_mon/rotwing_viewer.py +++ b/sw/ground_segment/python/rotwing_mon/rotwing_viewer.py @@ -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() \ No newline at end of file