From 676acd5cc47828bc92c2fad1e474a39aacbd2ba4 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Sat, 12 Aug 2006 23:25:38 +0000 Subject: Merged jcorgan/ezdop into trunk. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@3264 221aa14e-8319-0410-a670-987f0aec2ac5 --- ezdop/src/host/hunter/AUTHORS | 1 + ezdop/src/host/hunter/ChangeLog | 0 ezdop/src/host/hunter/Makefile.am | 1 + ezdop/src/host/hunter/NEWS | 0 ezdop/src/host/hunter/README | 11 + ezdop/src/host/hunter/bootstrap | 28 + ezdop/src/host/hunter/config/hunter_ftdi.m4 | 18 + ezdop/src/host/hunter/config/hunter_wx.m4 | 11 + ezdop/src/host/hunter/configure.ac | 42 + ezdop/src/host/hunter/src/Makefile.am | 42 + ezdop/src/host/hunter/src/calibrate.cc | 82 ++ ezdop/src/host/hunter/src/calibrate.h | 53 ++ ezdop/src/host/hunter/src/doppler.cc | 517 +++++++++++ ezdop/src/host/hunter/src/doppler.h | 130 +++ ezdop/src/host/hunter/src/gps.cc | 247 ++++++ ezdop/src/host/hunter/src/gps.h | 119 +++ ezdop/src/host/hunter/src/histogram.cc | 170 ++++ ezdop/src/host/hunter/src/histogram.h | 89 ++ ezdop/src/host/hunter/src/hunter.bmp | Bin 0 -> 3126 bytes ezdop/src/host/hunter/src/hunter.cc | 1269 +++++++++++++++++++++++++++ ezdop/src/host/hunter/src/hunter.h | 156 ++++ ezdop/src/host/hunter/src/hunter.xpm | 39 + ezdop/src/host/hunter/src/hunter.xrc | 1020 +++++++++++++++++++++ ezdop/src/host/hunter/src/hunterapp.cc | 52 ++ ezdop/src/host/hunter/src/hunterapp.h | 43 + ezdop/src/host/hunter/src/known.cc | 55 ++ ezdop/src/host/hunter/src/known.h | 59 ++ ezdop/src/host/hunter/src/sample.cc | 139 +++ ezdop/src/host/hunter/src/sample.h | 97 ++ ezdop/src/host/hunter/src/samplelog.cc | 107 +++ ezdop/src/host/hunter/src/samplelog.h | 63 ++ ezdop/src/host/hunter/src/search.cc | 211 +++++ ezdop/src/host/hunter/src/search.h | 107 +++ ezdop/src/host/hunter/src/serial.cc | 216 +++++ ezdop/src/host/hunter/src/serial.h | 56 ++ ezdop/src/host/hunter/src/settings.cpp | 310 +++++++ ezdop/src/host/hunter/src/settings.h | 101 +++ ezdop/src/host/hunter/src/spherical.cc | 76 ++ ezdop/src/host/hunter/src/spherical.h | 45 + ezdop/src/host/hunter/src/tactical.cc | 142 +++ ezdop/src/host/hunter/src/tactical.h | 73 ++ ezdop/src/host/hunter/src/util.h | 79 ++ 42 files changed, 6076 insertions(+) create mode 100644 ezdop/src/host/hunter/AUTHORS create mode 100644 ezdop/src/host/hunter/ChangeLog create mode 100644 ezdop/src/host/hunter/Makefile.am create mode 100644 ezdop/src/host/hunter/NEWS create mode 100644 ezdop/src/host/hunter/README create mode 100755 ezdop/src/host/hunter/bootstrap create mode 100644 ezdop/src/host/hunter/config/hunter_ftdi.m4 create mode 100644 ezdop/src/host/hunter/config/hunter_wx.m4 create mode 100644 ezdop/src/host/hunter/configure.ac create mode 100644 ezdop/src/host/hunter/src/Makefile.am create mode 100644 ezdop/src/host/hunter/src/calibrate.cc create mode 100644 ezdop/src/host/hunter/src/calibrate.h create mode 100644 ezdop/src/host/hunter/src/doppler.cc create mode 100644 ezdop/src/host/hunter/src/doppler.h create mode 100644 ezdop/src/host/hunter/src/gps.cc create mode 100644 ezdop/src/host/hunter/src/gps.h create mode 100644 ezdop/src/host/hunter/src/histogram.cc create mode 100644 ezdop/src/host/hunter/src/histogram.h create mode 100644 ezdop/src/host/hunter/src/hunter.bmp create mode 100644 ezdop/src/host/hunter/src/hunter.cc create mode 100644 ezdop/src/host/hunter/src/hunter.h create mode 100644 ezdop/src/host/hunter/src/hunter.xpm create mode 100644 ezdop/src/host/hunter/src/hunter.xrc create mode 100644 ezdop/src/host/hunter/src/hunterapp.cc create mode 100644 ezdop/src/host/hunter/src/hunterapp.h create mode 100644 ezdop/src/host/hunter/src/known.cc create mode 100644 ezdop/src/host/hunter/src/known.h create mode 100644 ezdop/src/host/hunter/src/sample.cc create mode 100644 ezdop/src/host/hunter/src/sample.h create mode 100644 ezdop/src/host/hunter/src/samplelog.cc create mode 100644 ezdop/src/host/hunter/src/samplelog.h create mode 100644 ezdop/src/host/hunter/src/search.cc create mode 100644 ezdop/src/host/hunter/src/search.h create mode 100644 ezdop/src/host/hunter/src/serial.cc create mode 100644 ezdop/src/host/hunter/src/serial.h create mode 100644 ezdop/src/host/hunter/src/settings.cpp create mode 100644 ezdop/src/host/hunter/src/settings.h create mode 100644 ezdop/src/host/hunter/src/spherical.cc create mode 100644 ezdop/src/host/hunter/src/spherical.h create mode 100644 ezdop/src/host/hunter/src/tactical.cc create mode 100644 ezdop/src/host/hunter/src/tactical.h create mode 100644 ezdop/src/host/hunter/src/util.h (limited to 'ezdop/src/host/hunter') diff --git a/ezdop/src/host/hunter/AUTHORS b/ezdop/src/host/hunter/AUTHORS new file mode 100644 index 000000000..cdb61c9f1 --- /dev/null +++ b/ezdop/src/host/hunter/AUTHORS @@ -0,0 +1 @@ +Johnathan Corgan diff --git a/ezdop/src/host/hunter/ChangeLog b/ezdop/src/host/hunter/ChangeLog new file mode 100644 index 000000000..e69de29bb diff --git a/ezdop/src/host/hunter/Makefile.am b/ezdop/src/host/hunter/Makefile.am new file mode 100644 index 000000000..af437a64d --- /dev/null +++ b/ezdop/src/host/hunter/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = src diff --git a/ezdop/src/host/hunter/NEWS b/ezdop/src/host/hunter/NEWS new file mode 100644 index 000000000..e69de29bb diff --git a/ezdop/src/host/hunter/README b/ezdop/src/host/hunter/README new file mode 100644 index 000000000..d0368d842 --- /dev/null +++ b/ezdop/src/host/hunter/README @@ -0,0 +1,11 @@ +The files in this directory are a prototype implementation of the AE6HO +radio-location system. It will not compile under the gnuradio build +system at this time, nor does the source code use the libezdop interface +to the EZ-Doppler hardware. The build does not recurse into this directory +nor does the distribution tarball contain these files. + +Over time this functionality will be migrated in the 'gnuradio way' over +to the gr-rdf component. + +The files here are not officially part of the GNU Radio project, but are +here in the archive under GPL license as noted in the files. diff --git a/ezdop/src/host/hunter/bootstrap b/ezdop/src/host/hunter/bootstrap new file mode 100755 index 000000000..d8d180a61 --- /dev/null +++ b/ezdop/src/host/hunter/bootstrap @@ -0,0 +1,28 @@ +#!/bin/sh + +# Copyright 2001,2005 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio 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. +# +# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + + +rm -fr config.cache autom4te*.cache + +aclocal -I config +autoconf +autoheader +automake --add-missing diff --git a/ezdop/src/host/hunter/config/hunter_ftdi.m4 b/ezdop/src/host/hunter/config/hunter_ftdi.m4 new file mode 100644 index 000000000..64977e803 --- /dev/null +++ b/ezdop/src/host/hunter/config/hunter_ftdi.m4 @@ -0,0 +1,18 @@ +AC_DEFUN([HUNTER_FTDI],[ + AC_LANG_PUSH(C) + + AC_CHECK_HEADER([ftdi.h],[],[ + AC_MSG_ERROR(["Hunter requires ftdi.h, not found, stop."])] + ) + + save_LIBS="$LIBS" + AC_SEARCH_LIBS(ftdi_init, [ftdi], [FTDI_LIBS=$LIBS],[ + AC_MSG_ERROR(["Hunter requires libftdi, not found, stop."])] + ) + + LIBS="$save_LIBS" + AC_LANG_POP(C) + + AC_SUBST(FTDI_LIBS) + AC_DEFINE([HAVE_LIBFTDI],[1],[Define to 1 if your system has libftdi.]) +]) diff --git a/ezdop/src/host/hunter/config/hunter_wx.m4 b/ezdop/src/host/hunter/config/hunter_wx.m4 new file mode 100644 index 000000000..5d6dd08e7 --- /dev/null +++ b/ezdop/src/host/hunter/config/hunter_wx.m4 @@ -0,0 +1,11 @@ +AC_DEFUN([HUNTER_WX], [ + AC_PATH_PROG([WXCONFIG],[wx-config],[no]) + if test $WXCONFIG = no; then + AC_MSG_ERROR(["wxWidgets is required, not found, stop."]) + fi + + WX_FLAGS=`$WXCONFIG --cflags` + WX_LIBS=`$WXCONFIG --libs` + AC_SUBST(WX_FLAGS) + AC_SUBST(WX_LIBS) +]) diff --git a/ezdop/src/host/hunter/configure.ac b/ezdop/src/host/hunter/configure.ac new file mode 100644 index 000000000..b6bd4cf7e --- /dev/null +++ b/ezdop/src/host/hunter/configure.ac @@ -0,0 +1,42 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.59) +AC_INIT(hunter, 0.1svn, jcorgan@aeinet.com) +AM_INIT_AUTOMAKE + +AC_CONFIG_SRCDIR([src/hunter.cc]) +AC_CONFIG_HEADER([config.h]) + +# Checks for programs. +AC_PROG_CXX +AC_PROG_CC + +# Checks for libraries. + +# Checks for header files. +AC_CHECK_HEADERS([fcntl.h sys/ioctl.h termios.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_HEADER_STDBOOL +AC_C_CONST +AC_C_INLINE +AC_TYPE_SIZE_T +AC_STRUCT_TM + +# Checks for library functions. +AC_PROG_GCC_TRADITIONAL +AC_HEADER_STDC +AC_FUNC_MKTIME +AC_CHECK_FUNCS([modf sqrt]) + +# Application specific checks +HUNTER_WX +HUNTER_FTDI + +AC_CONFIG_FILES([ \ + Makefile + src/Makefile +]) + +AC_OUTPUT diff --git a/ezdop/src/host/hunter/src/Makefile.am b/ezdop/src/host/hunter/src/Makefile.am new file mode 100644 index 000000000..d1fe5eec2 --- /dev/null +++ b/ezdop/src/host/hunter/src/Makefile.am @@ -0,0 +1,42 @@ +# Copyright 2006 Johnathan Corgan. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation. +# +# This software 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 GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +bin_PROGRAMS = hunter +hunter_SOURCES = \ + calibrate.cc \ + doppler.cc \ + gps.cc \ + histogram.cc \ + hunter.cc \ + hunterapp.cc \ + hunter.xrc \ + known.cc \ + resource.cc \ + sample.cc \ + samplelog.cc \ + search.cc \ + serial.cc \ + settings.cpp \ + spherical.cc \ + tactical.cc + +hunter_CXXFLAGS = $(WX_FLAGS) +hunter_LDADD = \ + $(FTDI_LIBS) \ + $(WX_LIBS) + +resource.cc: hunter.xrc + wxrc -c -o resource.cc hunter.xrc diff --git a/ezdop/src/host/hunter/src/calibrate.cc b/ezdop/src/host/hunter/src/calibrate.cc new file mode 100644 index 000000000..2d4117f5e --- /dev/null +++ b/ezdop/src/host/hunter/src/calibrate.cc @@ -0,0 +1,82 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "calibrate.h" +#include "hunter.h" + +// wxWidgets includes +#include +#include +#include +#include +#include + +// Event table for CalibrationDialog +BEGIN_EVENT_TABLE(CalibrationDialog, wxDialog) + EVT_BUTTON(XRCID("calibration_start_button"), CalibrationDialog::OnStart) + EVT_BUTTON(XRCID("calibration_cancel_button"), CalibrationDialog::OnCancelOrZero) +END_EVENT_TABLE() + +CalibrationDialog::CalibrationDialog(HunterFrame *parent) +{ + m_hunter_frame = parent; + wxXmlResource::Get()->LoadDialog(this, parent, _T("calibration_dialog")); + m_start_button = XRCCTRL(*this, "calibration_start_button", wxButton); + m_cancel_button = XRCCTRL(*this, "calibration_cancel_button", wxButton); + m_progress_gauge = XRCCTRL(*this, "calibration_progress_gauge", wxGauge); + m_instructions_text = XRCCTRL(*this, "calibration_instructions_text", wxStaticText); + m_progress_text = XRCCTRL(*this, "calibration_progress_text", wxStaticText); + m_cancelled = false; +} + +void CalibrationDialog::OnStart(wxCommandEvent &event) +{ + wxLogDebug(_T("CalibrationDialog::OnStart()")); + wxString msg; + + m_start_button->Disable(); + m_equalized = false; + for (int i = 0; i < 6; i++) { + msg.Printf(_T("Peforming calibration step #%i"), i+1); + m_progress_text->SetLabel(msg); + if (m_cancelled) + break; + wxYield(); + m_hunter_frame->DoCalibrationStep(i); + if (m_cancelled) + break; + m_progress_gauge->SetValue(i+1); + } + m_equalized = true; + m_instructions_text->SetLabel(_T("After pressing OK, you will need to\n" \ + "set the signal source to straight ahead\n" \ + "and press the Doppler 'Zero' button.")); + m_progress_text->SetLabel(_T("Calibration completed.")); + m_cancel_button->SetLabel(_T("OK")); +} + +void CalibrationDialog::OnCancelOrZero(wxCommandEvent &event) +{ + wxLogDebug(_T("CalibrationDialog::OnCancel()")); + if (!m_equalized) { + m_cancelled = true; + } + + EndModal(0); +} diff --git a/ezdop/src/host/hunter/src/calibrate.h b/ezdop/src/host/hunter/src/calibrate.h new file mode 100644 index 000000000..7f3f399e2 --- /dev/null +++ b/ezdop/src/host/hunter/src/calibrate.h @@ -0,0 +1,53 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __CALIBRATE_H__ +#define __CALIBRATE_H__ + +// wxWidgets includes +#include +#include +#include // Can't forward declare because wxGauge is a macro + +// Forward declarations +class wxStaticText; +class HunterFrame; +class wxButton; + +class CalibrationDialog : public wxDialog +{ +public: + CalibrationDialog(HunterFrame *parent); + +private: + void OnStart(wxCommandEvent &event); + void OnCancelOrZero(wxCommandEvent &event); + + HunterFrame *m_hunter_frame; + wxGauge *m_progress_gauge; + wxStaticText *m_progress_text; + wxStaticText *m_instructions_text; + wxButton *m_start_button; + wxButton *m_cancel_button; + bool m_cancelled; + bool m_equalized; + + DECLARE_EVENT_TABLE(); +}; + +#endif // __CALIBRATE_H__ diff --git a/ezdop/src/host/hunter/src/doppler.cc b/ezdop/src/host/hunter/src/doppler.cc new file mode 100644 index 000000000..1e7b3cf49 --- /dev/null +++ b/ezdop/src/host/hunter/src/doppler.cc @@ -0,0 +1,517 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "doppler.h" +#include "util.h" +#include + +// wxWidgets includes +#include +#include + +// System level includes +#include + +#define SAMPLERATE 8000 +#define QUANTUM 0.2 // Sample period in seconds +#define MAXSAMPLE 0x3FF // 12 bit ADC + +#define DEFAULT_SELECTED_ROTATION_RATE 2 // 500 Hz until told otherwise +#define DEFAULT_FILTER_LEVEL 20 + +#define NORMALIZEPHASE(x) \ + if ((x) > M_PI) \ + (x) -= 2*M_PI; \ + if ((x) < -M_PI) \ + (x) += 2*M_PI; + +unsigned char rotation_rates[] = { + 8, // 250 Hz + 5, // 400 Hz + 4, // 500 Hz + 3, // 666 Hz + 2, // 1000 Hz + 1 // 2000 Hz +}; + +const wxEventType wxEVT_DOPPLER_UPDATE = wxNewEventType(); + +EZDopplerUpdate::EZDopplerUpdate(const wxEventType &event, float &in_phase, + float &quadrature, float &volume) : +wxNotifyEvent(event) +{ + m_in_phase = in_phase; + m_quadrature = quadrature; + m_volume = volume; +} + +DopplerBackground::DopplerBackground(wxWindow *window, EZDoppler *doppler) +{ + wxASSERT(window); + wxASSERT(doppler); + + m_running = false; + m_dest = window; + m_doppler = doppler; + Create(); +} + +// It's in thread.h but somehow gets undef'd +typedef void *ExitCode; +ExitCode DopplerBackground::Entry() +{ + float in_phase, quadrature, phase, magnitude, volume, rflevel; + + m_running = true; + while (!TestDestroy()) { + if (m_doppler->Sample((int)(QUANTUM*SAMPLERATE), in_phase, quadrature, volume)) { + EZDopplerUpdate update(wxEVT_DOPPLER_UPDATE, in_phase, quadrature, volume); + wxPostEvent(m_dest, update); + } + } + m_running = false; +} + +EZDoppler::EZDoppler(wxWindow *gui) +{ + wxASSERT(gui); + + m_thread = NULL; + m_online = false; + m_selected_rate = DEFAULT_SELECTED_ROTATION_RATE; + m_gui = gui; + m_in_phase = 0.0; + m_quadrature = 0.0; + m_alpha = 1.0/(DEFAULT_FILTER_LEVEL*200); + m_beta = 1.0-m_alpha; + m_phase = 0.0; + m_offset = 0.0; + + for(int i = 0; i < NUM_RATES; i++) + m_calibration[i] = 0.0; + +#if HAVE_LIBFTDI + m_device = new struct ftdi_context; + wxASSERT(m_device); + if (ftdi_init(m_device)) { + wxLogWarning(_T("ftdi_init: %s"), m_device->error_str); + return; + } +#endif + +} + +EZDoppler::~EZDoppler() +{ + if (m_online) { + wxLogMessage(_T("EZDoppler::~EZDoppler(): doppler still online in destructor, finalizing")); + Finalize(); + } +#if HAVE_LIBFTDI + wxASSERT(m_device); + ftdi_deinit(m_device); + delete m_device; +#endif +} + +bool EZDoppler::Initialize() +{ + m_online = false; + +#if HAVE_LIBFTDI + if (ftdi_usb_open(m_device, EZDOP_VENDORID, EZDOP_PRODUCTID)) { + wxLogDebug(_T("ftdi_usb_open: %s"), m_device->error_str); + return false; + } +#elif HAVE_LIBFTD2XX + if ((m_status = FT_Open(0, &m_handle)) != FT_OK) { + wxLogError(_T("FT_Open failed: %i"), m_status); + return false; + } +#endif + + m_online = true; + if (m_online) + Reset(); + + return m_online; + } + +bool EZDoppler::Finalize() +{ + if (!m_online) + return true; + + if (m_thread && m_thread->IsRunning()) { + wxLogDebug(_T("EZDoppler::Finalize: finalizing a running doppler")); + Stop(); + } + +#if HAVE_LIBFTDI + if (ftdi_usb_close(m_device)) { + wxLogWarning(_T("ftdi_usb_close: %s"), m_device->error_str); + return false; + } +#elif HAVE_LIBFTD2XX + if ((m_status = FT_Close(m_handle)) != FT_OK) { + wxLogWarning(_T("FT_Close failed: %i"), m_status); + return false; + } +#endif + + m_online = false; + return true; +} + +bool EZDoppler::IsOnline() +{ + return m_online; +} + +bool EZDoppler::send_byte(unsigned char data) +{ + wxASSERT(m_online); +#if HAVE_LIBFTDI + if (ftdi_write_data(m_device, &data, 1) != 1) { + wxLogWarning(_T("ftdi_write_data: %s"), m_device->error_str); + return false; + } +#elif HAVE_LIBFTD2XX + DWORD written; + if ((m_status = FT_Write(m_handle, &data, 1, &written)) != FT_OK || written != 1) { + wxLogError(_T("FT_Write failed: %i"), m_status); + return false; + } +#endif + return true; +} + +bool EZDoppler::Reset() +{ + wxASSERT(m_online); + + if (m_thread && m_thread->IsRunning()) { + wxLogDebug(_T("EZDoppler::Reset: resetting running doppler")); + Stop(); + } + + + // Reset FTDI chipset +#if HAVE_LIBFTDI + if (ftdi_usb_reset(m_device)) { + wxLogWarning(_T("ftdi_usb_reset: %s"), m_device->error_str); + return false; + } +#elif HAVE_LIBFTD2XX + if ((m_status = FT_ResetDevice(m_handle) != FT_OK)) { + wxLogError(_T("FT_ResetDevice failed: %i"), m_status); + return false; + } +#endif + + // Set FTDI chipset baudrate for bitbang +#if HAVE_LIBFTDI + if (ftdi_set_baudrate(m_device, EZDOP_BAUDRATE)) { + wxLogWarning(_T("ftdi_set_baudrate: %s"), m_device->error_str); + return false; + } +#elif HAVE_LIBFTD2XX + if ((m_status = FT_SetBaudRate(m_handle, EZDOP_BAUDRATE)) != FT_OK) { + wxLogError(_T("FT_SetBaudRate failed: %i"), m_status); + return false; + } +#endif + + // Toggle DTR (-->AVR RESET) +#if HAVE_LIBFTDI + // Enable bitbang + if (ftdi_enable_bitbang(m_device, EZDOP_BBDIR)) { + wxLogWarning(_T("ftdi_enable_bitbang: %s"), m_device->error_str); + return false; + } + + // Lower DTR by writing 0 to bitbang output + if (!send_byte(0x00)) // HMMM: this actually lowers all outputs, of course + return false; +#elif HAVE_LIBFTD2XX + // Set DTR line (goes low) to reset AVR and delay + if ((m_status = FT_SetDtr(m_handle)) != FT_OK) { + wxLogError(_T("FT_SetDtr failed: %i"), m_status); + return false; + } +#endif + + // 10 ms sleep with RESET low + wxMilliSleep(10); + +#if HAVE_LIBFTDI + // Now raise DTR by writing 1 to bitbang output + if (!send_byte(0xFF)) + return false; + + if (ftdi_disable_bitbang(m_device)) { + wxLogWarning(_T("ftdi_disable_bitbang: %s"), m_device->error_str); + return false; + } + + // Minimum chunk size for reads to reduce latency + if (ftdi_read_data_set_chunksize(m_device, 256)) { + wxLogWarning(_T("ftdi_read_data_set_chunksize: %s"), m_device->error_str); + return false; + } +#elif HAVE_LIBFTD2XX + if ((m_status = FT_ClrDtr(m_handle)) != FT_OK) { + wxLogError(_T("FT_ClrDtr failed: %i"), m_status); + return false; + } +#endif + + // 100 ms after RESET cleared to let things warm up + wxMilliSleep(100); + + m_selected_rate = DEFAULT_SELECTED_ROTATION_RATE; + + return true; +} + +bool EZDoppler::Start() +{ + wxASSERT(m_online); + // TODO: flush stream data + + if (!send_byte(EZDOP_CMD_ROTATE) || !send_byte(EZDOP_CMD_STREAM)) + return false; + + m_thread = new DopplerBackground(m_gui, this); + m_thread->Run(); +} + +bool EZDoppler::Stop() +{ + wxASSERT(m_online); + // TODO: flush stream data + + if (m_thread && m_thread->IsRunning()) { + m_thread->Delete(); + while (m_thread->IsRunning()) { + wxYield(); + } + } + + m_thread = NULL; + return (send_byte(EZDOP_CMD_STROFF) && send_byte(EZDOP_CMD_STOP)); +} + +bool EZDoppler::SelectRotationRate(int n) +{ + wxASSERT(m_online); + wxASSERT(n >= 0 && n < 6); + + unsigned char rate = rotation_rates[n]; + if (send_byte(EZDOP_CMD_RATE) && send_byte(rate)) { + m_selected_rate = n; + m_in_phase = 0.0; + m_quadrature = 0.0; + return true; + } + + return false; +} + +int EZDoppler::GetSelectedRotationRate() +{ + return m_selected_rate; +} + +bool EZDoppler::Zero() +{ + return true; +} + +bool EZDoppler::SetFilter(int n) +{ + wxASSERT(n > 0); + m_alpha = 1.0/(n*200); // Time constant is filter value divided by 5 (empirically determined) + m_beta = 1.0-m_alpha; + return true; +} + +bool EZDoppler::Sample(int nsamples, float &in_phase, float &quadrature, float &volume) +{ + unsigned short *audio = new unsigned short[nsamples*2]; + unsigned char *antenna = new unsigned char[nsamples]; + + unsigned int rd; + unsigned int count = 0; + + // Read samples from USB port, 2 bytes per sample + while (count < nsamples*2) { + unsigned int amt = nsamples*2-count; + unsigned char *ptr = (unsigned char *)&audio[count/2]; // if count is odd, causes frame slip? + if ((count/2)*2 != count) + wxLogDebug(_T("EZDoppler::Sample: count is odd (%i)"), count); +#if HAVE_LIBFTDI + rd = ftdi_read_data(m_device, ptr, amt); + if (rd < 0) { + wxLogWarning(_T("ftdi_read_data: %s"), m_device->error_str); + return false; // FIXME: memory leak for antenna and audio! + } + count += rd; +#elif HAVE_LIBFTD2XX + DWORD num; + FT_STATUS status = FT_Read(m_handle, ptr, amt, &num); + if (status != FT_OK) { + wxLogWarning(_T("FT_Read: %i"), status); + return false; // FIXME: memory leak for antenna and audio! + } + count += num; +#endif + } + + // Extract antenna array position from samples, flag unsynced if not a valid antenna value + bool sync = true; + for (int i = 0; i < nsamples; i++) { + unsigned char ant = (audio[i] & 0xF000) >> 12; + if (ant != 8 && ant != 4 && ant != 2 && ant != 1) + sync = false; + antenna[i] = ant; + audio[i] &= 0x03FF; + } + + // If not synced, throw away a byte in receive stream to resync + unsigned char dummy; + if (!sync) { + wxLogDebug(_T("EZDoppler::Sample: sync failure detected")); +#if HAVE_LIBFTDI + ftdi_read_data(m_device, &dummy, 1); +#elif HAVE_LIBFTD2XX + DWORD rd; + FT_Read(m_handle, &dummy, 1, &rd); +#endif + return false; // FIXME: memory leak for antenna and audio! + } + + // Calculate DC offset and max and min values + float sum = 0.0; + float mean = 0.0; + for (int i = 0; i < nsamples; i++) + sum += audio[i]; + mean = sum/nsamples; + + // Calculate doppler response + unsigned char ant; + float sample; + volume = 0.0; + for (int i = 0; i < nsamples; i++) { + ant = antenna[i]; + + // Subtract DC offset and scale to -1 to 1 + sample = 2*(((float)audio[i])-mean)/MAXSAMPLE; + + // Calculate peak volume + if (fabs(sample) > volume) + volume = fabs(sample); + + // Integrate and lowpass filter sample into I/Q based on which antenna is selected + // Order here creates a clockwise rotating I/Q phasor + switch(ant) { + case 8: + m_in_phase = m_in_phase*m_beta + sample*m_alpha; + break; + case 4: + m_quadrature = m_quadrature*m_beta - sample*m_alpha; + break; + case 2: + m_in_phase = m_in_phase*m_beta - sample*m_alpha; + break; + case 1: + m_quadrature = m_quadrature*m_beta + sample*m_alpha; + break; + default: + wxLogError(_T("EZDoppler::Sample: Unknown antenna value %i"), ant); + break; + } + } + + // m_phase is the actual instrument reading regardless of calibration + m_phase = atan2(m_quadrature, m_in_phase); + + // Calibration angle is sum of equalized offset and global offset + float cal = m_calibration[m_selected_rate] + m_offset; + + // Rotate I, Q by calibration angle + float i_cal = cos(cal); + float q_cal = sin(cal); + in_phase = m_in_phase*i_cal - m_quadrature*q_cal; + quadrature = m_quadrature*i_cal + m_in_phase*q_cal; + + delete antenna; + delete audio; + return true; +} + +bool EZDoppler::Calibrate(float phase) +{ + float offset = phase - m_phase; + NORMALIZEPHASE(offset); + m_calibration[m_selected_rate] = offset; + return true; +} + +bool EZDoppler::SetCalibration(int rate, float offset) +{ + wxASSERT(rate >= 0 && rate < 7); + if (rate < 6) + m_calibration[rate] = offset; + else + m_offset = offset; +} + +float EZDoppler::GetCalibration(int rate) +{ + wxASSERT(rate >= 0 && rate < 7); + if (rate < 6) + return m_calibration[rate]; + else + return m_offset; +} + +bool EZDoppler::SetOffset(float offset) +{ + m_offset = offset-m_phase-m_calibration[m_selected_rate]; + NORMALIZEPHASE(m_offset); + NORMALIZEPHASE(m_offset); + NORMALIZEPHASE(m_offset); +} + +bool EZDoppler::Nudge(float amount) +{ + float cal = m_calibration[m_selected_rate]; + cal += amount; + NORMALIZEPHASE(cal); + m_calibration[m_selected_rate] = cal; + return true; +} + +bool EZDoppler::NudgeAll(float amount) +{ + m_offset += amount; + NORMALIZEPHASE(m_offset); + return true; +} diff --git a/ezdop/src/host/hunter/src/doppler.h b/ezdop/src/host/hunter/src/doppler.h new file mode 100644 index 000000000..1471de6a4 --- /dev/null +++ b/ezdop/src/host/hunter/src/doppler.h @@ -0,0 +1,130 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __DOPPLER_H__ +#define __DOPPLER_H__ + +// Autoconf generated configure options +#if HAVE_CONFIG_H + #include "config.h" +#endif + +// USB access library +#if HAVE_LIBFTDI + #include +#elif HAVE_LIBFTD2XX + #if __WIN32__ + #include + #endif + #include +#endif + +#include + +#define NUM_RATES 6 + +class EZDoppler; + +class DopplerBackground : public wxThread +{ +public: + DopplerBackground(wxWindow *window, EZDoppler *doppler); + virtual ExitCode Entry(); + bool IsRunning() { return m_running; } + +private: + bool m_running; + wxWindow *m_dest; + EZDoppler *m_doppler; +}; + +class EZDopplerUpdate : public wxNotifyEvent +{ +public: + EZDopplerUpdate(const wxEventType &event, float &in_phase, float &quadrature, + float &volume); + virtual wxEvent *Clone() const { return new EZDopplerUpdate(*this); } + + float m_in_phase; + float m_quadrature; + float m_volume; +}; + +extern const wxEventType wxEVT_DOPPLER_UPDATE; + +typedef void(wxEvtHandler::*EZDopplerUpdateFunction)(EZDopplerUpdate&); + +#define EVT_DOPPLER_UPDATE(fn) \ + DECLARE_EVENT_TABLE_ENTRY( \ + wxEVT_DOPPLER_UPDATE, -1, -1, \ + (wxObjectEventFunction)(wxEventFunction)(EZDopplerUpdateFunction)&fn, \ + (wxObject *)NULL \ + ), + +class EZDoppler +{ +public: + EZDoppler(wxWindow *gui); + ~EZDoppler(); + + // Control commands + bool Initialize(); + bool Finalize(); + bool IsOnline(); + bool Start(); + bool Stop(); + bool Zero(); + bool SetFilter(int n); + bool SelectRotationRate(int n); + int GetSelectedRotationRate(); + bool Reset(); + bool Sample(int nsamples, float &in_phase, float &quadrature, float &volume); + bool Calibrate(float phase); + bool SetCalibration(int rate, float offset); + float GetCalibration(int rate); + bool SetOffset(float offset = 0.0); + bool Nudge(float amount); + bool NudgeAll(float amount); + +private: + // USB interaction +#if HAVE_LIBFTDI + struct ftdi_context *m_device; // libftdi device instance data +#elif HAVE_LIBFTD2XX + FT_HANDLE m_handle; // FTD2XX device instance data + FT_STATUS m_status; // FTD2XX device function call results +#endif + bool send_byte(unsigned char data); + + // Doppler control + bool m_online; + int m_selected_rate; + wxWindow *m_gui; + DopplerBackground *m_thread; + + // DSP state + float m_in_phase; // Filtered I value + float m_quadrature; // Filtered Q value + float m_alpha; // Exponential lowpass constant + float m_beta; // Exponential lowpass constant = 1-alpha + float m_phase; // Actual phase of doppler before calibration + float m_offset; // Global calibration angle + float m_calibration[NUM_RATES]; // Individual rotation rate offset +}; + +#endif // __DOPPLER_H__ diff --git a/ezdop/src/host/hunter/src/gps.cc b/ezdop/src/host/hunter/src/gps.cc new file mode 100644 index 000000000..c891251e8 --- /dev/null +++ b/ezdop/src/host/hunter/src/gps.cc @@ -0,0 +1,247 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "gps.h" +#include "serial.h" + +// wxWidgets includes +#include + +#define NMEA_BAUD 4800 +#define NMEA_BUFSIZE 82 // Maximum NMEA sentence length + +const wxEventType wxEVT_GPS_UPDATE = wxNewEventType(); + +GPSUpdate::GPSUpdate(const wxEventType &event, GPRMC *gprmc) : +wxNotifyEvent(event) +{ + m_gprmc = gprmc; +} + +GPSBackground::GPSBackground(GPS *gps) +{ + wxLogDebug(_T("GPSBackground::GPSBackground()")); + wxASSERT(gps); + + m_running = false; + m_gps = gps; + m_port = gps->GetPort(); + Create(); +} + +// It's in thread.h but somehow gets undef'd +typedef void *ExitCode; +ExitCode GPSBackground::Entry() +{ + wxLogDebug(_T("GPSBackground::GPSBackground: entry")); + + m_running = true; + while (!TestDestroy()) + PerLoop(); + m_running = false; + + wxLogDebug(_T("GPSBackground::GPSBackground: exit")); +} + +void GPSBackground::PerLoop() +{ + static char buffer[NMEA_BUFSIZE]; + static int offset = 0; + + while(m_port->RxReady() > 0) { + while (offset < NMEA_BUFSIZE) { + // Read a byte into the buffer from the GPS data + if (m_port->Read(&buffer[offset], 1) != 1) + return; // No more to read or read error/timeout, bail + + // Test for end of NMEA message + if (buffer[offset] == '\r' || buffer[offset] == '\n') { + buffer[offset] = '\0'; // Append end of string null + if (strlen(buffer)) + m_gps->RxData(buffer); + offset = 0; + } + else + offset++; + } + + wxLogDebug(_T("GPSBackground: discarding too long input")); + offset = 0; + } + + wxMilliSleep(500); +} + +GPS::GPS(wxEvtHandler *dest) +{ + wxLogDebug(_T("GPS::GPS()")); + m_thread = NULL; + m_dest = dest; +} + +GPS::~GPS() +{ + wxLogDebug(_T("GPS::~GPS()")); +} + +bool GPS::Start(wxString &port) +{ + wxLogDebug(_T("GPS::Start(): %s"), port.c_str()); + m_port = new SerialPort(port); + + if (m_port->Open(NMEA_BAUD) == false) { + delete m_port; + return false; + } + + m_thread = new GPSBackground(this); + m_thread->Run(); + return true; +} + +bool GPS::Stop() +{ + wxLogDebug(_T("GPS::Stop()")); + + if (m_thread && m_thread->IsRunning()) { + m_thread->Delete(); + while (m_thread->IsRunning()) { + wxYieldIfNeeded(); + } + } + + m_thread = NULL; + + m_port->Close(); + if (m_port) + delete m_port; + + return true; +} + +bool NMEA::Checksum(char *sentence) +{ + unsigned char checksum = '\0'; + char ch, *pos = sentence, ctxt[3]; + + while ((ch = *pos++) != '*' && ch != '\0') + checksum ^= ch; + + sprintf(ctxt, "%02X", checksum); + if (strncmp(ctxt, pos, 2)) + return false; + else + return true; +} + +char *NMEA::Field(char *sentence, int num) +{ + static char result[NMEA_BUFSIZE]; + char ch, *pos = sentence; + + while (num-- > 0) + while ((ch = *pos++) != ',' && ch != '\0') + continue; + + strncpy(result, pos, NMEA_BUFSIZE-1); + int i = 0; + pos = result; + while (*pos && *pos != ',' && *pos != '*' && *pos != '\r' && ++i < NMEA_BUFSIZE) + pos++; + + *pos = 0; + return result; +} + +double NMEA::Coord(char *sentence, int num) +{ + double coord, degrees, minutes; + + sscanf(Field(sentence, num), "%lf", &coord); + minutes = 100.0*modf(coord/100.0, °rees); + coord = degrees+minutes/60.0; + + char *ptr = Field(sentence, num+1); + if (*ptr == 'S' || *ptr == 'W') + coord = -coord; + + return coord; +} + +void GPS::RxData(char *buffer) +{ + wxASSERT(buffer); + + if (NMEA::Checksum(buffer+1)) { + if (strncmp("$GPRMC", buffer, 6) == 0) { + GPRMC *fix = new GPRMC(buffer); + GPSUpdate update(wxEVT_GPS_UPDATE, fix); + wxPostEvent(m_dest, update); + } + } + else + wxLogDebug(_T("GPS::RxData: NMEA checksum failed for input")); +} + +GPRMC::GPRMC(char *sentence) +{ + wxASSERT(sentence); + + struct tm stamp; + char digits[2]; + + char *p = Field(sentence, 1); + wxASSERT(p); + strncpy(digits, p, 2); + stamp.tm_hour = atoi(digits); + strncpy(digits, p+2, 2); + stamp.tm_min = atoi(digits); + strncpy(digits, p+4, 2); + stamp.tm_sec = atoi(digits); + + p = Field(sentence, 9); + wxASSERT(p); + strncpy(digits, p, 2); + stamp.tm_mday = atoi(digits); + strncpy(digits, p+2, 2); + stamp.tm_mon = atoi(digits)-1; + strncpy(digits, p+4, 2); + stamp.tm_year = atoi(digits)+100; + + m_stamp = mktime(&stamp); + m_valid = !strcmp(Field(sentence, 2), "A"); + m_fix.SetLatitude(Coord(sentence, 3)); + m_fix.SetLongitude(Coord(sentence, 5)); + sscanf(Field(sentence, 7), "%f", &m_speed); + sscanf(Field(sentence, 8), "%f", &m_heading); + sscanf(Field(sentence, 10), "%f", &m_magnetic); + if (!strcmp(Field(sentence, 11), "W")) + m_magnetic = -m_magnetic; + m_mode = *Field(sentence, 12); +} + +void GPRMC::AsString(char *buf) +{ + sprintf(buf, "%s %lf %lf %f %f %f, %s", + ctime(&m_stamp), + m_fix.Latitude(), + m_fix.Longitude(), + m_speed, m_heading, m_magnetic, + m_valid ? "valid" : "invalid"); +} diff --git a/ezdop/src/host/hunter/src/gps.h b/ezdop/src/host/hunter/src/gps.h new file mode 100644 index 000000000..da0d8e4c8 --- /dev/null +++ b/ezdop/src/host/hunter/src/gps.h @@ -0,0 +1,119 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __GPS_H__ +#define __GPS_H__ + +// Autoconf generated configure options +#if HAVE_CONFIG_H + #include "config.h" +#endif + +// Application level includes +#include "spherical.h" + +// wxWidgets includes +#include + +// System level includes +#include // For some reason, doesn't include modf needed in gps.cpp + +class GPRMC; + +class GPSUpdate : public wxNotifyEvent +{ +public: + GPSUpdate(const wxEventType &event, GPRMC *gprmc); + virtual wxEvent *Clone() const { return new GPSUpdate(*this); } + GPRMC *m_gprmc; +}; + +extern const wxEventType wxEVT_GPS_UPDATE; + +typedef void(wxEvtHandler::*GPSUpdateFunction)(GPSUpdate&); + +#define EVT_GPS_UPDATE(fn) \ + DECLARE_EVENT_TABLE_ENTRY( \ + wxEVT_GPS_UPDATE, -1, -1, \ + (wxObjectEventFunction)(wxEventFunction)(GPSUpdateFunction)&fn, \ + (wxObject *)NULL \ + ), + +class NMEA +{ +public: + static bool Checksum(char *sentence); + static char *Field(char *sentence, int num); + static double Coord(char *sentence, int num); +}; + +class GPRMC : public NMEA +{ +public: + GPRMC(char *sentence); + void AsString(char *buf); + + time_t m_stamp; + bool m_valid; + Spherical m_fix; + float m_speed; + float m_heading; + float m_magnetic; + unsigned char m_mode; +}; + +class GPS; +class SerialPort; + +class GPSBackground : public wxThread +{ +public: + GPSBackground(GPS *gps); + virtual ExitCode Entry(); + bool IsRunning() { return m_running; } + +private: + void PerLoop(); + + bool m_running; + GPS *m_gps; + SerialPort *m_port; +}; + +class wxString; + +class GPS +{ +public: + GPS(wxEvtHandler *dest); + ~GPS(); + + bool Start(wxString &port); + bool Stop(); + SerialPort *GetPort() { return m_port; } + void RxData(char *buffer); + +private: + void RxGPRMC(char *buffer); + + GPSBackground *m_thread; + wxEvtHandler *m_dest; + SerialPort *m_port; +}; + +#endif // __GPS_H__ diff --git a/ezdop/src/host/hunter/src/histogram.cc b/ezdop/src/host/hunter/src/histogram.cc new file mode 100644 index 000000000..abadad5e6 --- /dev/null +++ b/ezdop/src/host/hunter/src/histogram.cc @@ -0,0 +1,170 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "histogram.h" + +// wxWidgets includes +#include +#include +#include + +using namespace std; + +ErrorHistogram::ErrorHistogram() +{ + Reset(); +} + +void ErrorHistogram::Reset() +{ + m_bins = vector(360); + m_mode = 0; + m_modal_frequency = 0.0; + m_isum = 0.0; + m_qsum = 0.0; + m_msum = 0.0; + m_conc = 0.0; + m_mean = 0.0; + m_count = 0; +} + +void ErrorHistogram::Calc(const vector &samples, const Spherical &location) +{ + Reset(); + for (int i = 0; i < samples.size(); i++) { + const Sample &sample = samples[i]; + float angle, ierror, qerror; + sample.CalcError(location, angle, ierror, qerror); + Add(angle, sample.Strength(), ierror, qerror); + } + Normalize(); +} + +void ErrorHistogram::Add(float angle, float magnitude, float ierror, float qerror) +{ + int index = (int)(angle+180.0); + while (index > 359) + index -= 360; + while (index < 0) + index += 360; + wxASSERT(index >= 0 && index < 360); + + float freq = m_bins[index] += magnitude; + if (freq > m_modal_frequency) { + m_modal_frequency = freq; + m_mode = index-180; + } + + m_isum += ierror; + m_qsum += qerror; + m_msum += magnitude; + m_count++; +} + +// This turns the histogram into an actual PDF +void ErrorHistogram::Normalize() +{ + if (m_msum == 0.0) + return; + + for (int i = 0; i < 360; i++) + m_bins[i] = m_bins[i]/(m_msum); + + m_modal_frequency /= m_msum; + m_conc = (m_isum*m_isum+m_qsum*m_qsum)/(m_msum*m_msum); + if (m_conc > 0.0) + m_mean = atan2(m_qsum, m_isum)*180.0/M_PI; +} + +// Event table for HistogramPanel +BEGIN_EVENT_TABLE(HistogramPanel, wxPanel) + EVT_PAINT(HistogramPanel::OnPaint) + EVT_SIZE(HistogramPanel::OnSize) +END_EVENT_TABLE() + +HistogramPanel::HistogramPanel(wxWindow *parent) : +wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) +{ + SetBackgroundColour(*wxBLACK); + m_mode = Rectangular; +} + +void HistogramPanel::SetData(const ErrorHistogram &histogram) +{ + m_histogram = histogram; // Copy constructor invoked + Refresh(); +} + +void HistogramPanel::OnPaint(wxPaintEvent &event) +{ + wxPaintDC dc(this); + draw_panel(dc); +} + +void HistogramPanel::draw_panel(wxDC &dc) +{ + const vector &data = m_histogram.Data(); + if (m_histogram.Count() == 0) + return; + + if (m_mode == Polar) { + // Draw histogram bars + dc.SetPen(wxPen(*wxRED, 1, wxSOLID)); + for (int i = 0; i < 360; i++) { + float len = data[i]*(m_extent*0.75-10)/m_histogram.ModalFrequency(); + float radians = i*M_PI/180.0; + wxPoint tip = wxPoint((int)(m_center.x-sin(radians)*len), + (int)(m_center.y+cos(radians)*len)); + + dc.DrawLine(m_center, tip); + } + } + else if (m_mode == Rectangular) { + // Draw zero tick + dc.SetPen(wxPen(*wxWHITE, 1, wxSOLID)); + dc.DrawLine(m_center.x, 0, m_center.x, 10); + + // Draw mode tick + dc.SetPen(wxPen(*wxGREEN, 1, wxSOLID)); + int mode = (int)((m_histogram.Mode()+180)/360.0*m_width); + dc.DrawLine(mode, 0, mode, 9); + + // Draw histogram bars + dc.SetPen(wxPen(*wxRED, 1, wxSOLID)); + float freq = m_histogram.ModalFrequency(); + for (int i = 0; i < 360; i++) { + int len = (int)(data[i]/freq*(m_height-10)); + int pos = (int)(i/360.0*m_width); + dc.DrawLine(pos, m_height, pos, m_height-len); + } + } +} + +void HistogramPanel::OnSize(wxSizeEvent &event) +{ + GetClientSize(&m_width, &m_height); + m_center = wxPoint(m_width/2, (int)(m_height*0.75)); + + if (m_width > m_height) + m_extent = m_height; + else + m_extent = m_width; + + Refresh(); +} diff --git a/ezdop/src/host/hunter/src/histogram.h b/ezdop/src/host/hunter/src/histogram.h new file mode 100644 index 000000000..99486ff31 --- /dev/null +++ b/ezdop/src/host/hunter/src/histogram.h @@ -0,0 +1,89 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __HISTOGRAM_H__ +#define __HISTOGRAM_H__ + +// Application level includes +#include "sample.h" + +// wxWidgets includes +#include + +// System level includes +#include + +class ErrorHistogram +{ +public: + ErrorHistogram(); + + void Reset(); + void Calc(const std::vector &samples, const Spherical &location); + void Add(float angle, float magnitude, float ierror, float qerror); // analytic errors are -pi to pi + void Normalize(); + + int Count() const { return m_count; } + int Mode() const { return m_mode; } + float ModalFrequency() const { return m_modal_frequency; } + float Mean() const { return m_mean; } + float Concentration() const { return m_conc; } + const std::vector &Data() const { return m_bins; } + +private: + int m_mode; + float m_modal_frequency; + float m_isum; + float m_qsum; + float m_msum; + float m_conc; + float m_mean; + int m_count; + std::vector m_bins; +}; + +class HistogramPanel : public wxPanel +{ +public: + enum Mode { Rectangular, Polar }; + + HistogramPanel(wxWindow *parent); + void SetData(const ErrorHistogram &histogram); + void SetMode(Mode mode) { m_mode = mode; Refresh(); } + + // Event handlers + void OnPaint(wxPaintEvent &event); + void OnSize(wxSizeEvent &event); + +private: + void draw_panel(wxDC &dc); + ErrorHistogram m_histogram; + + // State + Mode m_mode; + + // Window size derived parameters + wxPoint m_center; + int m_width; + int m_height; + int m_extent; + + DECLARE_EVENT_TABLE(); +}; + +#endif diff --git a/ezdop/src/host/hunter/src/hunter.bmp b/ezdop/src/host/hunter/src/hunter.bmp new file mode 100644 index 000000000..6d67dd46d Binary files /dev/null and b/ezdop/src/host/hunter/src/hunter.bmp differ diff --git a/ezdop/src/host/hunter/src/hunter.cc b/ezdop/src/host/hunter/src/hunter.cc new file mode 100644 index 000000000..39b8325c8 --- /dev/null +++ b/ezdop/src/host/hunter/src/hunter.cc @@ -0,0 +1,1269 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application includes +#include "hunter.h" +#include "hunter.xpm" +#include "doppler.h" +#include "tactical.h" +#include "calibrate.h" +#include "settings.h" +#include "gps.h" +#include "serial.h" +#include "search.h" +#include "util.h" +#include "spherical.h" +#include "sample.h" +#include "samplelog.h" +#include "known.h" +#include "histogram.h" + +// wxWidgets includes +#include +#include // Too many to do individually +#include // hmmm, fails compile on mingw32 without it + +// Event table for HunterFrame +BEGIN_EVENT_TABLE(HunterFrame, wxFrame) + // Application level events + EVT_CLOSE(HunterFrame::OnClose) + + // File menu events + EVT_MENU(XRCID("file_new_menuitem"), HunterFrame::OnFileNew) + EVT_MENU(XRCID("file_open_menuitem"), HunterFrame::OnFileOpen) + EVT_MENU(XRCID("file_close_menuitem"), HunterFrame::OnFileClose) + EVT_MENU(XRCID("file_save_menuitem"), HunterFrame::OnFileSave) + EVT_MENU(XRCID("file_exit_menuitem"), HunterFrame::OnExit) + + // Doppler menu events + EVT_MENU(XRCID("doppler_toggle_menuitem"), HunterFrame::OnDopplerToggle) + EVT_MENU(XRCID("doppler_autostart_menuitem"), HunterFrame::OnDopplerAutostart) + EVT_MENU(XRCID("doppler_reset_menuitem"), HunterFrame::OnDopplerReset) + + // Calibration menu events + EVT_MENU(XRCID("calibration_savetofile_menuitem"), HunterFrame::OnCalibrationSaveToFile) + EVT_MENU(XRCID("calibration_loadfromfile_menuitem"), HunterFrame::OnCalibrationLoadFromFile) + EVT_MENU(XRCID("calibration_loadtransmitter_menuitem"), HunterFrame::OnCalibrationLoadTransmitter) + EVT_MENU(XRCID("calibration_savetransmitter_menuitem"), HunterFrame::OnCalibrationSaveTransmitter) + + // About menu events + EVT_MENU(XRCID("about_menuitem"), HunterFrame::OnAbout) + + // Status panel events + EVT_RADIOBOX(XRCID("statistics_source_radiobox"), HunterFrame::OnHistogramSourceChg) + EVT_RADIOBOX(XRCID("statistics_coords_radiobox"), HunterFrame::OnHistogramCoordsChg) + + // Doppler tab events + EVT_BUTTON(XRCID("doppler_toggle_button"), HunterFrame::OnDopplerToggle) + EVT_CHECKBOX(XRCID("doppler_autostart_checkbox"), HunterFrame::OnDopplerAutostart) + EVT_BUTTON(XRCID("doppler_reset_button"), HunterFrame::OnDopplerReset) + EVT_COMMAND_SCROLL(XRCID("doppler_filter_slider"), HunterFrame::OnDopplerFilterChg) + EVT_RADIOBOX(XRCID("doppler_rotation_radiobox"), HunterFrame::OnDopplerRotationChg) + EVT_DOPPLER_UPDATE(HunterFrame::OnDopplerUpdate) + + // GPS tab events + EVT_BUTTON(XRCID("gps_toggle_button"), HunterFrame::OnGPSToggle) + EVT_CHECKBOX(XRCID("gps_autostart_checkbox"), HunterFrame::OnGPSAutostart) + EVT_COMBOBOX(XRCID("gps_device_combobox"), HunterFrame::OnGPSDeviceSelect) + EVT_TEXT_ENTER(XRCID("gps_device_combobox"), HunterFrame::OnGPSDeviceSelect) + EVT_GPS_UPDATE(HunterFrame::OnGPSUpdate) + + // Calibration tab events + EVT_BUTTON(XRCID("calibration_equalize_button"), HunterFrame::OnCalibrationEqualize) + EVT_BUTTON(XRCID("calibration_zero_button"), HunterFrame::OnCalibrationZero) + EVT_SPIN_UP(XRCID("calibration_adjust_spinner"), HunterFrame::OnCalibrationAdjustRight) + EVT_SPIN_DOWN(XRCID("calibration_adjust_spinner"), HunterFrame::OnCalibrationAdjustLeft) + EVT_CHECKBOX(XRCID("calibration_all_checkbox"), HunterFrame::OnCalibrationAffectAllRates) + EVT_BUTTON(XRCID("known_transmitter_update_button"), HunterFrame::OnKnownTransmitterUpdate) + EVT_CHECKBOX(XRCID("known_transmitter_checkbox"), HunterFrame::OnUseKnownTransmitter) + + // Search tab events + EVT_BUTTON(XRCID("search_newsave_button"), HunterFrame::OnSearchNewSave) + EVT_BUTTON(XRCID("search_openclose_button"), HunterFrame::OnSearchOpenClose) + EVT_BUTTON(XRCID("search_toggle_button"), HunterFrame::OnSearchToggle) + EVT_BUTTON(XRCID("search_once_button"), HunterFrame::OnSearchOnce) + EVT_SEARCH_UPDATE(HunterFrame::OnSearchUpdate) + + // Display tab events + EVT_RADIOBOX(XRCID("display_orientation_radiobox"), HunterFrame::OnDisplayOrientation) + EVT_CHECKBOX(XRCID("display_doppler_checkbox"), HunterFrame::OnDisplayDoppler) + EVT_CHECKBOX(XRCID("display_known_checkbox"), HunterFrame::OnDisplayKnown) + EVT_CHECKBOX(XRCID("display_estimated_checkbox"), HunterFrame::OnDisplayEstimated) + +END_EVENT_TABLE() + +HunterFrame::HunterFrame() : +wxFrame(), +m_search(this) +{ + m_settings = NULL; + m_doppler = NULL; + m_gps = NULL; + m_log = NULL; + m_tactical_panel = NULL; + m_error_histogram_panel = NULL; + + m_doppler_started = false; + m_gps_started = false; + m_capture = false; + m_one_shot = false; + m_histogram_source = 0; + + InitializeSettings(); + InitializeWindows(); + InitializeDoppler(); + InitializeGPS(); + InitializeCalibration(); + InitializeSearch(); + InitializeDisplay(); +} + +void HunterFrame::InitializeSettings() +{ + m_settings = new HunterSettings(); + wxSetWorkingDirectory(m_settings->GetWorkingDirectory()); +} + +void HunterFrame::InitializeWindows() +{ + // Build main window controls + bool loaded = wxXmlResource::Get()->LoadFrame(this, NULL, wxT("main_frame")); + wxASSERT(loaded); + + // Hack until XRC understands for wxSplitterWindow!! + XRCCTRL(*this, "horiz_splitter", wxSplitterWindow)->SetSashGravity(1.0); + + // Increase font size for better visibility in certain text displays + wxFont font = XRCCTRL(*this, "doppler_relative_bearing_text", wxStaticText)->GetFont(); + float points = font.GetPointSize()*2.0; + font.SetPointSize((int)points); + XRCCTRL(*this, "doppler_relative_bearing_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "doppler_absolute_bearing_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "transmitter_estimated_bearing_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "transmitter_estimated_range_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "transmitter_actual_bearing_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "transmitter_actual_range_text", wxStaticText)->SetFont(font); + points *= 0.75; + font.SetPointSize((int)points); + XRCCTRL(*this, "gps_latitude_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "gps_longitude_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "gps_heading_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "gps_speed_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "search_latitude_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "search_longitude_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "search_count_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "search_status_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "search_mode_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "search_mean_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "search_score_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "search_disterror_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "error_count_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "error_mode_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "error_mean_text", wxStaticText)->SetFont(font); + XRCCTRL(*this, "error_conc_text", wxStaticText)->SetFont(font); + + // Create the menu + SetMenuBar(wxXmlResource::Get()->LoadMenuBar(wxT("main_menu"))); + CreateStatusBar(1); + SetIcon(wxIcon(hunter_xpm)); + + // Restore saved window size and position + int x = m_settings->GetWindowXPos(); + int y = m_settings->GetWindowYPos(); + wxSize size = m_settings->GetWindowSize(); + int hsplitpos = size.GetHeight()-170; // HACK! + SetSize(x, y, size.GetWidth(), size.GetHeight()); + XRCCTRL(*this, "horiz_splitter", wxSplitterWindow)->SetSashPosition(hsplitpos); + + // Error histogram is a custom control outside XRC system + m_error_histogram_panel = new HistogramPanel(this); + wxXmlResource::Get()->AttachUnknownControl(wxT("error_histogram_panel"), + m_error_histogram_panel, this); +} + +void HunterFrame::InitializeDoppler() +{ + m_doppler = new EZDoppler(this); + m_doppler->Initialize(); + m_doppler_started = false; + SetDopplerParams(); +} + +void HunterFrame::InitializeGPS() +{ + m_gps = new GPS(this); + m_gps_started = false; + + // Populate device selection combobox + XRCCTRL(*this, "gps_device_combobox", wxComboBox)->Clear(); + wxArrayString ports = EnumerateSerialPorts(); + for (int i = 0; i < ports.GetCount(); i++) + XRCCTRL(*this, "gps_device_combobox", wxComboBox)->Append(ports[i]); + XRCCTRL(*this, "gps_device_combobox", wxComboBox)->SetValue(m_settings->GetGPSDeviceName()); + + // GPS autostart + if (m_settings->GetGPSAutostart()) { + XRCCTRL(*this, "gps_autostart_checkbox", wxCheckBox)->SetValue(true); + StartGPS(); + } +} + +void HunterFrame::InitializeCalibration() +{ + XRCCTRL(*this, "calibration_all_checkbox", wxCheckBox)->SetValue(m_settings->GetCalibrationAffectAllRates()); + + if (m_settings->GetUseKnownTransmitter()) { + m_known.Location(Spherical(m_settings->GetKnownTransmitterLatitude(), + m_settings->GetKnownTransmitterLongitude())); + XRCCTRL(*this, "known_transmitter_checkbox", wxCheckBox)->SetValue(true); + } + + UpdateKnownLocation(); +} + +void HunterFrame::InitializeDisplay() +{ + // Tactical display is a custom control outside XRC system + m_tactical_panel = new TacticalPanel(this); + wxXmlResource::Get()->AttachUnknownControl(wxT("tactical_panel"), + m_tactical_panel, this); + + if (m_settings->GetDisplayOrientation()) { + XRCCTRL(*this, "display_orientation_radiobox", wxRadioBox)->SetSelection(1); + m_tactical_panel->SetOrientation(NorthUp); + } + + if (m_settings->GetDisplayDoppler()) { + XRCCTRL(*this, "display_doppler_checkbox", wxCheckBox)->SetValue(true); + m_tactical_panel->SetDisplayDoppler(true); + } + + if (m_settings->GetDisplayKnown()) { + XRCCTRL(*this, "display_known_checkbox", wxCheckBox)->SetValue(true); + m_tactical_panel->SetDisplayKnown(true); + } + + if (m_settings->GetDisplayEstimated()) { + XRCCTRL(*this, "display_estimated_checkbox", wxCheckBox)->SetValue(true); + m_tactical_panel->SetDisplayEstimated(true); + } +} + +void HunterFrame::InitializeSearch() +{ + EnableSearchItems(false); +} + +void HunterFrame::SetDopplerParams() +{ + // NOTE: This is not in InitializeDoppler() as it needs to be called + // separately when resetting Doppler + + // Adjust windows based on device status + if (!m_doppler->IsOnline()) { + // Disable all GUI elements associated with Doppler + + // Doppler control tab + XRCCTRL(*this, "doppler_control_panel", wxPanel)->Disable(); + + // Doppler menu items + this->GetMenuBar()->FindItem(XRCID("doppler_toggle_menuitem"))->Enable(false); + this->GetMenuBar()->FindItem(XRCID("doppler_autostart_menuitem"))->Enable(false); + this->GetMenuBar()->FindItem(XRCID("doppler_reset_menuitem"))->Enable(false); + + // Calibration control tab + XRCCTRL(*this, "calibration_equalize_button", wxButton)->Enable(false); + XRCCTRL(*this, "calibration_zero_button", wxButton)->Enable(false); + XRCCTRL(*this, "calibration_adjust_spinner", wxSpinButton)->Enable(false); + + return; + } + + // Doppler filter level + int filter = m_settings->GetDopplerFilter(); + XRCCTRL(*this, "doppler_filter_slider", wxSlider)->SetValue(filter); + wxScrollEvent dummy; + OnDopplerFilterChg(dummy); + + // Doppler rotation rate + int rate = m_settings->GetDopplerRotation(); + XRCCTRL(*this, "doppler_rotation_radiobox", wxRadioBox)->SetSelection(rate); + wxCommandEvent dummy2; + OnDopplerRotationChg(dummy2); + + // Doppler calibration values + for (int i=0; i < 7; i++) { // i==6 gets zero offset + float offset = m_settings->GetDopplerCalibration(i); + m_doppler->SetCalibration(i, offset); + } + + // Doppler autostart + if (m_settings->GetDopplerAutostart()) { + this->GetMenuBar()->FindItem(XRCID("doppler_autostart_menuitem"))->Check(true); + XRCCTRL(*this, "doppler_autostart_checkbox", wxCheckBox)->SetValue(true); + StartDoppler(); + } +} + +void HunterFrame::StartDoppler() +{ + m_doppler->Start(); + m_doppler_started = true; + XRCCTRL(*this, "doppler_toggle_button", wxButton)->SetLabel(_T("Stop")); + XRCCTRL(*this, "calibration_equalize_button", wxButton)->Enable(); + XRCCTRL(*this, "calibration_zero_button", wxButton)->Enable(); + XRCCTRL(*this, "calibration_adjust_spinner", wxSpinButton)->Enable(); + this->GetMenuBar()->FindItem(XRCID("doppler_toggle_menuitem"))->SetText(_T("&Stop")); + this->GetMenuBar()->FindItem(XRCID("doppler_reset_menuitem"))->Enable(true); +} + +void HunterFrame::StopDoppler() +{ + m_doppler->Stop(); + m_doppler_started = false; + UpdateDopplerStatus(false); + XRCCTRL(*this, "doppler_toggle_button", wxButton)->SetLabel(_T("Start")); + this->GetMenuBar()->FindItem(XRCID("doppler_toggle_menuitem"))->SetText(_("&Start")); + this->GetMenuBar()->FindItem(XRCID("doppler_reset_menuitem"))->Enable(false); + XRCCTRL(*this, "calibration_equalize_button", wxButton)->Disable(); + XRCCTRL(*this, "calibration_zero_button", wxButton)->Disable(); + XRCCTRL(*this, "calibration_adjust_spinner", wxSpinButton)->Disable(); +} + +void HunterFrame::StartGPS() +{ + wxString port = XRCCTRL(*this, "gps_device_combobox", wxComboBox)->GetValue(); + if (!m_gps->Start(port)) { + wxMessageDialog(this, wxT("Failed to start GPS!"), wxT("GPS Error"), + wxOK|wxICON_ERROR).ShowModal(); + return; + } + + m_gps_started = true; + XRCCTRL(*this, "gps_toggle_button", wxButton)->SetLabel(_T("Stop")); + XRCCTRL(*this, "gps_device_combobox", wxComboBox)->Disable(); + XRCCTRL(*this, "display_orientation_radiobox", wxRadioBox)->Enable(); + if (XRCCTRL(*this, "display_orientation_radiobox", wxRadioBox)->GetSelection() == 1) { + m_tactical_panel->SetOrientation(NorthUp); + } + + if (m_search.HasSolution()) + UpdateSearchDirection(true); + + UpdateKnownDirection(); + UpdateKnownStatistics(); +} + +void HunterFrame::StopGPS() +{ + m_gps->Stop(); + m_gps_started = false; + m_tactical_panel->SetOrientation(TrackUp); + m_tactical_panel->SetActualBearing(-1.0); + m_tactical_panel->SetEstimatedBearing(-1.0); + XRCCTRL(*this, "gps_toggle_button", wxButton)->SetLabel(_T("Start")); + XRCCTRL(*this, "gps_device_combobox", wxComboBox)->Enable(); + XRCCTRL(*this, "display_orientation_radiobox", wxRadioBox)->Disable(); + + UpdateGPSStatus(false); + UpdateSearchDirection(false); + UpdateKnownDirection(); + UpdateKnownStatistics(); + + // Note: can't replace with call to UpdateDopplerStatus as we only want to clear one field + XRCCTRL(*this, "doppler_absolute_bearing_text", wxStaticText)->SetLabel(_T("")); +} + +void HunterFrame::EnableSearchItems(bool enable) +{ + XRCCTRL(*this, "search_toggle_button", wxButton)->Enable(enable); + XRCCTRL(*this, "search_once_button", wxButton)->Enable(enable); + + // These fields will get populated when samples come in + UpdateSearchStatus(false); + UpdateSearchLocation(false); + UpdateSearchDirection(false); + + this->GetMenuBar()->FindItem(XRCID("file_save_menuitem"))->Enable(enable); + this->GetMenuBar()->FindItem(XRCID("file_close_menuitem"))->Enable(enable); + + if (!enable) { + XRCCTRL(*this, "search_toggle_button", wxButton)->SetLabel(_T("Stop")); + XRCCTRL(*this, "search_toggle_button", wxButton)->SetLabel(_T("Automatic")); + } +} + +void HunterFrame::OnClose(wxCloseEvent &event) +{ + wxCommandEvent dummy; + + // Cleanup for this scope should be done here + // TODO: factor out into methods corresponding to start up initialization + + // Save window size and position + int x, y; + GetPosition(&x, &y); + m_settings->SetWindowXPos(x); + m_settings->SetWindowYPos(y); + m_settings->SetWindowSize(GetSize()); + + wxASSERT(m_doppler != NULL); + if (m_doppler_started) + StopDoppler(); + m_doppler->Finalize(); + if (m_gps_started) + StopGPS(); + + if (m_log) + OnFileClose(dummy); + + + m_settings->SetWorkingDirectory(wxGetCwd()); + + delete m_doppler; + delete m_gps; + delete m_settings; + if (m_log) + delete m_log; + + // Last thing to do + Destroy(); +} + +void HunterFrame::OnExit(wxCommandEvent &event) +{ + // Cleanup for this scope should be done in ::OnClose, not here. This + // method is not called when exiting from the system menu, but rather it + // goes straight to ::OnClose + + // Sends close event + this->Close(); +} + +void HunterFrame::OnAbout(wxCommandEvent &event) +{ + wxMessageBox(wxT("Copyright(C) 2005 Johnathan Corgan"), + wxT("About AE6HO Radio Location System"), + wxOK | wxICON_INFORMATION, this); +} + +void HunterFrame::OnDopplerToggle(wxCommandEvent &event) +{ + if (m_doppler_started) + StopDoppler(); + else + StartDoppler(); +} + +void HunterFrame::OnDopplerAutostart(wxCommandEvent &event) +{ + bool autostart = event.IsChecked(); + this->GetMenuBar()->FindItem(XRCID("doppler_autostart_menuitem"))->Check(autostart); + XRCCTRL(*this, "doppler_autostart_checkbox", wxCheckBox)->SetValue(autostart); + m_settings->SetDopplerAutostart(autostart); +} + +void HunterFrame::OnDopplerReset(wxCommandEvent &event) +{ + StopDoppler(); + m_doppler->Reset(); + SetDopplerParams(); // restarts Doppler if autostart is configured +} + +void HunterFrame::OnDopplerFilterChg(wxScrollEvent &event) +{ + int n = XRCCTRL(*this, "doppler_filter_slider", wxSlider)->GetValue(); + m_doppler->SetFilter(n); + m_settings->SetDopplerFilter(n); +} + +void HunterFrame::OnDopplerRotationChg(wxCommandEvent &event) +{ + int n = XRCCTRL(*this, "doppler_rotation_radiobox", wxRadioBox)->GetSelection(); + m_doppler->SelectRotationRate(n); + m_settings->SetDopplerRotation(n); +} + +void HunterFrame::OnDopplerUpdate(EZDopplerUpdate &event) +{ + if (!m_doppler_started) // Spurious event after doppler stopped + return; + + // Update current state variables + m_sample.Volume(event.m_volume); + m_sample.InPhase(event.m_in_phase); + m_sample.Quadrature(event.m_quadrature); + m_sample.Strength(sqrt(event.m_in_phase*event.m_in_phase + + event.m_quadrature*event.m_quadrature)); + m_sample.Phase(atan2(event.m_quadrature, event.m_in_phase)); + + UpdateDopplerStatus(true); + + if (m_log && m_gps_started && m_capture && + m_sample.Speed() >= 5.0 && m_sample.Valid()) { + m_log->Add(m_sample); + if (m_one_shot == true) { + StopCapture(); + CalcSolution(); + if (m_search.HasSolution()) { + UpdateSearchStatus(true); + UpdateSearchDirection(true); + } + } + } +} + +void HunterFrame::UpdateDopplerStatus(bool display) +{ + wxString str; + if (!display) { + m_tactical_panel->SetDopplerBearing(-1.0); + XRCCTRL(*this, "doppler_level_gauge", wxGauge)->SetValue(0); + XRCCTRL(*this, "doppler_audio_gauge", wxGauge)->SetValue(0); + XRCCTRL(*this, "doppler_relative_bearing_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "doppler_absolute_bearing_text", wxStaticText)->SetLabel(_T("")); + return; + } + + float display_relative_bearing = degree_normalize(to_degrees(m_sample.Phase())); + if (m_tactical_panel) + m_tactical_panel->SetDopplerBearing(display_relative_bearing); + + int display_magnitude = (int)limit((m_sample.Strength()*200.0), 0.0, 100.0); + XRCCTRL(*this, "doppler_level_gauge", wxGauge)->SetValue(display_magnitude); + + int display_amplitude = (int)limit((m_sample.Volume()*100.0), 0.0, 100.0); + XRCCTRL(*this, "doppler_audio_gauge", wxGauge)->SetValue(display_amplitude); + + str.Printf(_T("%03i"), degree_normalize((int)(display_relative_bearing+0.5))); // So zero is from -0.5 to 0.5 + XRCCTRL(*this, "doppler_relative_bearing_text", wxStaticText)->SetLabel(str); + + if (m_gps_started) { + str.Printf(_T("%03i"), degree_normalize((int)(m_sample.Heading()+display_relative_bearing))); + XRCCTRL(*this, "doppler_absolute_bearing_text", wxStaticText)->SetLabel(str); + } +} + +void HunterFrame::CalcKnownStatistics() +{ + if (m_log) + m_known.Calc(m_log->Samples()); + + UpdateKnownStatistics(); +} + +void HunterFrame::UpdateKnownStatistics() +{ + if (m_error_histogram_panel && m_histogram_source == 1) + m_error_histogram_panel->SetData(m_known.Histogram()); + + if (!m_known.HasStats()) { + XRCCTRL(*this, "error_count_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "error_mode_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "error_mean_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "error_conc_text", wxStaticText)->SetLabel(_T("")); + return; + } + + wxString str; + + str.Printf(_T("%i"), m_known.Count()); + XRCCTRL(*this, "error_count_text", wxStaticText)->SetLabel(str); + + str.Printf(_T("%i"), m_known.Mode()); + XRCCTRL(*this, "error_mode_text", wxStaticText)->SetLabel(str); + + str.Printf(_T("%5.1f"), m_known.Mean()); + XRCCTRL(*this, "error_mean_text", wxStaticText)->SetLabel(str); + + str.Printf(_T("%1.3f"), m_known.Concentration()); + XRCCTRL(*this, "error_conc_text", wxStaticText)->SetLabel(str); +} + +void HunterFrame::OnCalibrationEqualize(wxCommandEvent &event) +{ + CalibrationDialog *dlg = new CalibrationDialog(this); + dlg->ShowModal(); +} + +void HunterFrame::DoCalibrationStep(int which) +{ + static int delay; + + if (which == 0) { // Set up doppler + delay = XRCCTRL(*this, "doppler_filter_slider", wxSlider)->GetValue()/3; // Empirically determined + if (delay == 0) + delay = 1; + } + + // Set rotation rate for this step + wxCommandEvent dummy; + XRCCTRL(*this, "doppler_rotation_radiobox", wxRadioBox)->SetSelection(which); + OnDopplerRotationChg(dummy); + + // Wait until stable value + wxStartTimer(); + while(wxGetElapsedTime(false) < 1000*delay) + wxYield(); // eeewwww! + + // Now stable reading can be calibrated + m_doppler->Calibrate(-M_PI); // Sets to zero + + float offset=m_doppler->GetCalibration(which); + m_settings->SetDopplerCalibration(which, offset); +} + +void HunterFrame::OnCalibrationZero(wxCommandEvent &event) +{ + m_doppler->SetOffset(); + float offset=m_doppler->GetCalibration(6); + m_settings->SetDopplerCalibration(6, offset); +} + +void HunterFrame::OnCalibrationAdjustLeft(wxSpinEvent &event) +{ + if (m_settings->GetCalibrationAffectAllRates()) + m_doppler->NudgeAll(to_radians(-0.5)); + else + m_doppler->Nudge(to_radians(-0.5)); + + float offset=m_doppler->GetCalibration(6); + m_settings->SetDopplerCalibration(6, offset); +} + +void HunterFrame::OnCalibrationAdjustRight(wxSpinEvent &event) +{ + if (m_settings->GetCalibrationAffectAllRates()) + m_doppler->NudgeAll(to_radians(0.5)); + else + m_doppler->Nudge(to_radians(0.5)); + + float offset=m_doppler->GetCalibration(6); + m_settings->SetDopplerCalibration(6, offset); +} + +void HunterFrame::OnCalibrationAffectAllRates(wxCommandEvent &event) +{ + bool affect = event.IsChecked(); + XRCCTRL(*this, "calibration_all_checkbox", wxCheckBox)->SetValue(affect); + m_settings->SetCalibrationAffectAllRates(affect); +} + +void HunterFrame::OnGPSToggle(wxCommandEvent &event) +{ + if (m_gps_started) + StopGPS(); + else + StartGPS(); +} + +void HunterFrame::OnGPSAutostart(wxCommandEvent &event) +{ + bool start = XRCCTRL(*this, "gps_autostart_checkbox", wxCheckBox)->GetValue(); + m_settings->SetGPSAutostart(start); +} + +void HunterFrame::OnGPSDeviceSelect(wxCommandEvent &event) +{ + wxString device = XRCCTRL(*this, "gps_device_combobox", wxComboBox)->GetValue(); + m_settings->SetGPSDeviceName(device); +} + +void HunterFrame::OnGPSUpdate(GPSUpdate &update) +{ + // Update state variables + GPRMC *gprmc = update.m_gprmc; + m_sample.Valid(update.m_gprmc->m_valid); + m_sample.Time(update.m_gprmc->m_stamp); + m_sample.Location(update.m_gprmc->m_fix); + m_sample.Heading(update.m_gprmc->m_heading); + m_sample.Speed(update.m_gprmc->m_speed*1.15077945); // Conversion from knots to mph + m_sample.Rate(XRCCTRL(*this, "doppler_rotation_radiobox", wxRadioBox)->GetSelection()); + m_sample.Filtering(XRCCTRL(*this, "doppler_filter_slider", wxSlider)->GetValue()); + + UpdateGPSValidity(update.m_gprmc->m_valid); // Colors red for invalid, black for valid + UpdateGPSStatus(true); // gps lat, lon, heading, speed + UpdateKnownDirection(); // actual bearing and range + + CalcKnownStatistics(); + if (m_capture) + CalcSolution(); + if (m_search.HasSolution()) + UpdateSearchDirection(true); + + delete update.m_gprmc; +} + +void HunterFrame::CalcSolution() +{ + GetStatusBar()->SetStatusText(wxT("Searching...")); + m_search.Solve(m_log); +} + +void HunterFrame::OnSearchUpdate(SearchUpdate &update) +{ + if (update.m_done) + GetStatusBar()->SetStatusText(wxT("")); + + if (m_search.HasSolution()) { + UpdateSearchStatus(true); + UpdateSearchLocation(true); + if (m_gps_started) + UpdateSearchDirection(true); + } +} + +void HunterFrame::UpdateSearchStatus(bool display) +{ + wxString str; + + if (m_error_histogram_panel && m_histogram_source == 0) + m_error_histogram_panel->SetData(m_search.Histogram()); + + if (!display) { + XRCCTRL(*this, "search_count_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "search_status_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "search_mode_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "search_mean_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "search_score_text", wxStaticText)->SetLabel(_T("")); + return; + } + + str.Printf(_T("%i"), m_log->Count()); + XRCCTRL(*this, "search_count_text", wxStaticText)->SetLabel(str); + + str.Printf(_T("%s"), m_search.Busy() ? "BUSY" : ""); + XRCCTRL(*this, "search_status_text", wxStaticText)->SetLabel(str); + + str.Printf(_T("%i"), m_search.Mode()); + XRCCTRL(*this, "search_mode_text", wxStaticText)->SetLabel(str); + + str.Printf(_T("%6.1f"), m_search.Mean()); + XRCCTRL(*this, "search_mean_text", wxStaticText)->SetLabel(str); + + str.Printf(_T("%i"), (int)(m_search.Concentration()*100.0)); + XRCCTRL(*this, "search_score_text", wxStaticText)->SetLabel(str); +} + +void HunterFrame::UpdateGPSStatus(bool display) +{ + wxString str; + + if (!display) { + XRCCTRL(*this, "gps_latitude_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "gps_longitude_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "gps_heading_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "gps_speed_text", wxStaticText)->SetLabel(_T("")); + return; + } + + // Calculate latitude to display + str.Printf(_T("%1.5lf"), fabs(m_sample.Latitude())); + str.Append(m_sample.Latitude() < 0.0 ? _T("S") : _T("N")); + XRCCTRL(*this, "gps_latitude_text", wxStaticText)->SetLabel(str); + + // Calculate longitude to display + str.Printf(_T("%1.5lf"), fabs(m_sample.Longitude())); + str.Append(m_sample.Longitude() < 0.0 ? _T("W") : _T("E")); + XRCCTRL(*this, "gps_longitude_text", wxStaticText)->SetLabel(str); + + // Calculate heading to display + m_tactical_panel->SetHeading(m_sample.Heading()); + str.Printf(_T("%03i"), (unsigned int)(m_sample.Heading())); + XRCCTRL(*this, "gps_heading_text", wxStaticText)->SetLabel(str); + + // Calculate speed to display + str.Printf(_T("%i"), (unsigned int)(m_sample.Speed())); + XRCCTRL(*this, "gps_speed_text", wxStaticText)->SetLabel(str); +} + +void HunterFrame::UpdateSearchDirection(bool display) +{ + wxString str; + + if (!display) { + XRCCTRL(*this, "transmitter_estimated_bearing_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "transmitter_estimated_range_text", wxStaticText)->SetLabel(_T("")); + if (m_tactical_panel) + m_tactical_panel->SetEstimatedBearing(-1.0); + return; + } + + float estimated_bearing = + bearing(m_sample.Location(), m_search.GetEstimatedLocation()); + + float estimated_range = + range(m_sample.Location(), m_search.GetEstimatedLocation()); + + m_tactical_panel->SetEstimatedBearing(estimated_bearing); + str.Printf(_T("%03i"), degree_normalize((int)estimated_bearing)); + XRCCTRL(*this, "transmitter_estimated_bearing_text", wxStaticText)->SetLabel(str); + if (estimated_range > 99.9) + str.Printf(_T("%1.0f"), estimated_range); + else if (estimated_range > 9.99) + str.Printf(_T("%1.1f"), estimated_range); + else + str.Printf(_T("%1.2f"), estimated_range); + XRCCTRL(*this, "transmitter_estimated_range_text", wxStaticText)->SetLabel(str); +} + +void HunterFrame::UpdateSearchLocation(bool display) +{ + wxString str; + + if (!display) { + XRCCTRL(*this, "search_latitude_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "search_longitude_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "search_disterror_text", wxStaticText)->SetLabel(_T("")); + return; + } + + Spherical estimated_location = m_search.GetEstimatedLocation(); + + // Calculate latitude to display + str.Printf(_T("%1.5f"), fabs(estimated_location.Latitude())); + str.Append(estimated_location.Latitude() < 0.0 ? _T("S") : _T("N")); + XRCCTRL(*this, "search_latitude_text", wxStaticText)->SetLabel(str); + + // Calculate longitude to display + str.Printf(_T("%1.5f"), fabs(estimated_location.Longitude())); + str.Append(estimated_location.Longitude() < 0.0 ? _T("W") : _T("E")); + XRCCTRL(*this, "search_longitude_text", wxStaticText)->SetLabel(str); + + if (m_known.IsSet()) { + float distance_error = range(m_search.GetEstimatedLocation(), m_known.Location()); + if (distance_error > 99.9) + str.Printf(_T("%1.0f"), distance_error); + else if (distance_error > 9.99) + str.Printf(_T("%1.1f"), distance_error); + else + str.Printf(_T("%1.2f"), distance_error); + XRCCTRL(*this, "search_disterror_text", wxStaticText)->SetLabel(str); + } +} + +void HunterFrame::UpdateKnownDirection() +{ + wxString str; + + if (!m_known.IsSet() || !m_gps_started) { + XRCCTRL(*this, "transmitter_actual_bearing_text", wxStaticText)->SetLabel(_T("")); + XRCCTRL(*this, "transmitter_actual_range_text", wxStaticText)->SetLabel(_T("")); + if (m_tactical_panel) + m_tactical_panel->SetActualBearing(-1.0); + return; + } + + float actual_bearing = bearing(m_sample.Location(), m_known.Location()); + float actual_range = range(m_sample.Location(), m_known.Location()); + + m_tactical_panel->SetActualBearing(actual_bearing); + + str.Printf(_T("%03i"), degree_normalize((int)actual_bearing)); + XRCCTRL(*this, "transmitter_actual_bearing_text", wxStaticText)->SetLabel(str); + if (actual_range > 99.9) + str.Printf(_T("%1.0f"), actual_range); + else if (actual_range > 9.99) + str.Printf(_T("%1.1f"), actual_range); + else + str.Printf(_T("%1.2f"), actual_range); + XRCCTRL(*this, "transmitter_actual_range_text", wxStaticText)->SetLabel(str); +} + +void HunterFrame::UpdateKnownLocation() +{ + wxString str; + + if (!m_known.IsSet()) { + XRCCTRL(*this, "known_transmitter_latitude_edit", wxTextCtrl)->SetValue(_T("")); + XRCCTRL(*this, "known_transmitter_longitude_edit", wxTextCtrl)->SetValue(_T("")); + XRCCTRL(*this, "search_disterror_text", wxStaticText)->SetLabel(_T("")); + return; + } + + str.Printf(_T("%1.5f"), m_known.Latitude()); + XRCCTRL(*this, "known_transmitter_latitude_edit", wxTextCtrl)->SetValue(str); + str.Printf(_T("%1.5f"), m_known.Longitude()); + XRCCTRL(*this, "known_transmitter_longitude_edit", wxTextCtrl)->SetValue(str); + UpdateSearchLocation(true); // to update the known error +} + +void HunterFrame::OnKnownTransmitterUpdate(wxCommandEvent &event) +{ + // Note: this is an event handler for a calibration tab button + wxString lat_text = XRCCTRL(*this, "known_transmitter_latitude_edit", wxTextCtrl)->GetValue(); + wxString lon_text = XRCCTRL(*this, "known_transmitter_longitude_edit", wxTextCtrl)->GetValue(); + + double lat = 0.0, lon = 0.0; + + // TODO: use Spherical constructor/exceptions + if (!lat_text.ToDouble(&lat) || lat > 90.0 || lat < -90.0) { + wxMessageDialog(this, wxT("Invalid latitude entered."), wxT("Data Error"), + wxOK|wxICON_ERROR).ShowModal(); + return; + } + + if (!lon_text.ToDouble(&lon) || lon >180.0 || lon < -180.0) { + wxMessageDialog(this, wxT("Invalid longitude entered."), wxT("Data Error"), + wxOK|wxICON_ERROR).ShowModal(); + return; + } + + m_known.Location(Spherical(lat, lon)); + CalcKnownStatistics(); + + m_settings->SetKnownTransmitterLongitude(lon); + m_settings->SetKnownTransmitterLatitude(lat); + + UpdateKnownLocation(); + if (m_gps_started) + UpdateKnownDirection(); +} + +void HunterFrame::OnUseKnownTransmitter(wxCommandEvent &event) +{ + if (event.IsChecked()) + m_known.Location(Spherical(m_settings->GetKnownTransmitterLatitude(), + m_settings->GetKnownTransmitterLongitude())); + else + m_known.Clear(); + + m_settings->SetUseKnownTransmitter(m_known.IsSet()); + CalcKnownStatistics(); + UpdateKnownLocation(); + if (m_gps_started) + UpdateKnownDirection(); +} + +void HunterFrame::UpdateGPSValidity(bool valid) +{ + m_sample.Valid(valid); + + wxColour color = *wxBLACK; + if (!valid) + color = *wxRED; + + XRCCTRL(*this, "doppler_absolute_bearing_text", wxStaticText)->SetForegroundColour(color); + XRCCTRL(*this, "transmitter_actual_bearing_text", wxStaticText)->SetForegroundColour(color); + XRCCTRL(*this, "transmitter_actual_range_text", wxStaticText)->SetForegroundColour(color); + XRCCTRL(*this, "gps_latitude_text", wxStaticText)->SetForegroundColour(color); + XRCCTRL(*this, "gps_longitude_text", wxStaticText)->SetForegroundColour(color); + XRCCTRL(*this, "gps_heading_text", wxStaticText)->SetForegroundColour(color); + XRCCTRL(*this, "gps_speed_text", wxStaticText)->SetForegroundColour(color); +} + +void HunterFrame::OnFileNew(wxCommandEvent &event) +{ + if (m_log) { + wxLogDebug(_T("Not Implemented: current log is discarded without saving...")); + StopCapture(); + delete m_log; + m_search.Reset(); + m_log = NULL; + } + + MakeNewLogAndSearch(); + this->SetTitle(wxT("AE6HO Radio Location System - Unsaved Search")); // refactor into EnableSearchItems() + XRCCTRL(*this, "search_newsave_button", wxButton)->SetLabel(_T("Save")); + XRCCTRL(*this, "search_openclose_button", wxButton)->SetLabel(_T("Close")); + this->GetMenuBar()->FindItem(XRCID("file_new_menuitem"))->Enable(false); + this->GetMenuBar()->FindItem(XRCID("file_open_menuitem"))->Enable(false); + EnableSearchItems(true); +} + +void HunterFrame::MakeNewLogAndSearch() +{ + m_log = new SampleLog(); + m_search.Reset(); +} + +void HunterFrame::OnFileOpen(wxCommandEvent &event) +{ + wxString filename; + + if (m_log) + OnFileClose(event); + + wxFileDialog dlg(this, wxT("Open Log File"), _T("."), _T(""), _T("*.dat"), + wxOPEN|wxFILE_MUST_EXIST|wxCHANGE_DIR); + if (dlg.ShowModal() == wxID_OK) { + this->GetMenuBar()->FindItem(XRCID("file_new_menuitem"))->Enable(false); + this->GetMenuBar()->FindItem(XRCID("file_open_menuitem"))->Enable(false); + filename = dlg.GetPath(); + MakeNewLogAndSearch(); + m_log->Load(filename); + this->SetTitle(wxT("AE6HO Radio Location System - ")+filename); + XRCCTRL(*this, "search_newsave_button", wxButton)->SetLabel(_T("Save")); + XRCCTRL(*this, "search_openclose_button", wxButton)->SetLabel(_T("Close")); + EnableSearchItems(true); + CalcKnownStatistics(); + CalcSolution(); + } +} + +void HunterFrame::OnFileSave(wxCommandEvent &event) +{ + wxASSERT(m_log); + + wxString filename; + if (!m_log->HasFile()) { + wxFileDialog dlg(this, wxT("Save Search Data"), _T("."), _T(""), _T("*.dat"), + wxSAVE|wxOVERWRITE_PROMPT|wxCHANGE_DIR); + if (dlg.ShowModal() == wxID_OK) { + filename = dlg.GetPath(); + m_log->Save(filename); + this->SetTitle(wxT("AE6HO Radio Location System - ")+filename); + } + } + else + m_log->Save(); // Adds additional samples since last save +} + +void HunterFrame::OnFileClose(wxCommandEvent &event) +{ + // FIXME: ask user if they want to save changed data instead of going straight to save dialog + if (m_log->IsDirty()) + OnFileSave(event); + + StopCapture(); + delete m_log; + m_search.Reset(); + m_log = NULL; + + this->SetTitle(wxT("AE6HO Radio Location System")); // refactor into EnableSearchItems() + XRCCTRL(*this, "search_newsave_button", wxButton)->SetLabel(_T("New")); + XRCCTRL(*this, "search_openclose_button", wxButton)->SetLabel(_T("Open")); + this->GetMenuBar()->FindItem(XRCID("file_new_menuitem"))->Enable(true); + this->GetMenuBar()->FindItem(XRCID("file_open_menuitem"))->Enable(true); + EnableSearchItems(false); + m_known.ClearStats(); + UpdateKnownStatistics(); +} + +void HunterFrame::OnCalibrationSaveToFile(wxCommandEvent &event) +{ + wxString filename; + wxFileDialog dlg(this, wxT("Save Calibration Data"), _T("."), _T(""), _T("*.cal"), + wxSAVE|wxOVERWRITE_PROMPT|wxCHANGE_DIR); + if (dlg.ShowModal() == wxID_OK) { + filename = dlg.GetPath(); + wxFile file(filename.c_str(), wxFile::write); + for (int i = 0; i < 7; i++) { + wxString str; + float offset = m_settings->GetDopplerCalibration(i); + str.Printf(_T("%f\n"), offset); + file.Write(str.c_str(), str.length()); + } + } +} + +void HunterFrame::OnCalibrationLoadFromFile(wxCommandEvent &event) +{ + wxString filename; + wxFileDialog dlg(this, wxT("Load Calibration Data"), _T("."), _T(""), _T("*.cal"), + wxOPEN|wxFILE_MUST_EXIST|wxCHANGE_DIR); + if (dlg.ShowModal() == wxID_OK) { + filename = dlg.GetPath(); + wxFile file(filename.c_str(), wxFile::read); + for (int i = 0; i < 7; i++) { + char ch; + size_t count; + wxString line; + count = file.Read(&ch, 1); + while (count == 1 && ch != '\n') { + line.Append(ch); + count = file.Read(&ch, 1); + } + float offset = atof((char *)line.c_str()); + m_settings->SetDopplerCalibration(i, offset); + m_doppler->SetCalibration(i, offset); + } + } +} + +void HunterFrame::OnCalibrationSaveTransmitter(wxCommandEvent &event) +{ + wxString filename; + if (!m_known.IsSet()) + return; // FIXME: disable menu item when no known transmitter so we can't get here + + wxFileDialog dlg(this, wxT("Save Transmitter Coordinates"), _T("."), _T(""), _T("*.loc"), + wxSAVE|wxOVERWRITE_PROMPT|wxCHANGE_DIR); + if (dlg.ShowModal() == wxID_OK) { + filename = dlg.GetPath(); + wxFile file(filename.c_str(), wxFile::write); + wxString str; + // TODO: use Spherical method to output strings + str.Printf(_T("%f\n%f\n"), m_known.Latitude(), + m_known.Longitude()); + file.Write(str.c_str(), str.length()); + } +} + +void HunterFrame::OnCalibrationLoadTransmitter(wxCommandEvent &event) +{ + wxString filename; + wxFileDialog dlg(this, wxT("Load Transmitter Location"), _T("."), _T(""), _T("*.loc"), + wxOPEN|wxFILE_MUST_EXIST|wxCHANGE_DIR); + if (dlg.ShowModal() == wxID_OK) { + filename = dlg.GetPath(); + wxFile file(filename.c_str(), wxFile::read); + wxString latitude, longitude; + + for (int i = 0; i < 2; i++) { + char ch; + size_t count; + wxString line; + + count = file.Read(&ch, 1); + while (count == 1 && ch != '\n') { + line.Append(ch); + count = file.Read(&ch, 1); + } + if (i == 0) { + latitude = line; + } + else if (i == 1) { + longitude = line; + } + } + + m_known.Location(Spherical(latitude, longitude)); + CalcKnownStatistics(); + + XRCCTRL(*this, "known_transmitter_checkbox", wxCheckBox)->SetValue(true); + m_settings->SetUseKnownTransmitter(true); + m_settings->SetKnownTransmitterLatitude(m_known.Latitude()); + m_settings->SetKnownTransmitterLongitude(m_known.Longitude()); + + UpdateKnownLocation(); + if (m_gps_started) + UpdateKnownDirection(); + } +} + +void HunterFrame::OnSearchNewSave(wxCommandEvent &event) +{ + if (!m_log) + OnFileNew(event); + else + OnFileSave(event); +} + +void HunterFrame::OnSearchOpenClose(wxCommandEvent &event) +{ + if (!m_log) + OnFileOpen(event); + else + OnFileClose(event); +} + +void HunterFrame::OnSearchToggle(wxCommandEvent &event) +{ + if (!m_capture) + StartCaptureAutomatic(); + else + StopCapture(); +} + +void HunterFrame::StartCaptureAutomatic() +{ + m_capture = true; + m_one_shot = false; + XRCCTRL(*this, "search_toggle_button", wxButton)->SetLabel(_T("Stop")); + XRCCTRL(*this, "search_once_button", wxButton)->Enable(false); +} + +void HunterFrame::StartCaptureOnce() +{ + m_capture = true; + m_one_shot = true; + XRCCTRL(*this, "search_once_button", wxButton)->SetLabel(_T("Cancel")); + XRCCTRL(*this, "search_toggle_button", wxButton)->Enable(false); +} + +void HunterFrame::StopCapture() +{ + m_capture = false; + m_one_shot = false; + XRCCTRL(*this, "search_toggle_button", wxButton)->Enable(true); + XRCCTRL(*this, "search_once_button", wxButton)->Enable(true); + XRCCTRL(*this, "search_toggle_button", wxButton)->SetLabel(_T("Automatic")); + XRCCTRL(*this, "search_once_button", wxButton)->SetLabel(_T("One Shot")); +} + +void HunterFrame::OnSearchOnce(wxCommandEvent &event) +{ + if (!m_capture) + StartCaptureOnce(); + else + StopCapture(); +} + +void HunterFrame::OnDisplayOrientation(wxCommandEvent &event) +{ + int selection = XRCCTRL(*this, "display_orientation_radiobox", wxRadioBox)->GetSelection(); + if (selection == 0) + m_tactical_panel->SetOrientation(TrackUp); + else + m_tactical_panel->SetOrientation(NorthUp); + + m_settings->SetDisplayOrientation(selection); +} + +void HunterFrame::OnDisplayDoppler(wxCommandEvent &event) +{ + bool checked = event.IsChecked(); + m_tactical_panel->SetDisplayDoppler(checked); + m_settings->SetDisplayDoppler(checked); +} + +void HunterFrame::OnDisplayKnown(wxCommandEvent &event) +{ + bool checked = event.IsChecked(); + m_tactical_panel->SetDisplayKnown(checked); + m_settings->SetDisplayKnown(checked); +} + +void HunterFrame::OnDisplayEstimated(wxCommandEvent &event) +{ + bool checked = event.IsChecked(); + m_tactical_panel->SetDisplayEstimated(checked); + m_settings->SetDisplayEstimated(checked); +} + +void HunterFrame::OnHistogramSourceChg(wxCommandEvent &event) +{ + m_histogram_source = XRCCTRL(*this, "statistics_source_radiobox", wxRadioBox)->GetSelection(); + if (m_histogram_source == 0) + m_error_histogram_panel->SetData(m_search.Histogram()); + else if (m_histogram_source == 1) + m_error_histogram_panel->SetData(m_known.Histogram()); + // TODO: remember this in m_settings + Refresh(); // Needed? +} + +void HunterFrame::OnHistogramCoordsChg(wxCommandEvent &event) +{ + int n = XRCCTRL(*this, "statistics_coords_radiobox", wxRadioBox)->GetSelection(); + if (n == 0) + m_error_histogram_panel->SetMode(HistogramPanel::Rectangular); + else if (n == 1) + m_error_histogram_panel->SetMode(HistogramPanel::Polar); + // TODO: remember this in m_settings +} diff --git a/ezdop/src/host/hunter/src/hunter.h b/ezdop/src/host/hunter/src/hunter.h new file mode 100644 index 000000000..7710c5824 --- /dev/null +++ b/ezdop/src/host/hunter/src/hunter.h @@ -0,0 +1,156 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "sample.h" +#include "known.h" +#include "search.h" + +// wxWidgets includes +#include +#include // Unknown why these two aren't in wx.h +#include + +// Forward declarations +class EZDoppler; +class EZDopplerUpdate; +class GPS; +class GPSUpdate; +class HunterSettings; +class TacticalPanel; +class HistogramPanel; +class Spherical; +class SampleLog; +class KnownTransmitter; + +class HunterFrame : public wxFrame +{ +public: + // Constructor + HunterFrame(); + void DoCalibrationStep(int which); + +private: + // Initialization routines + void InitializeSettings(); + void InitializeWindows(); + void InitializeDoppler(); + void InitializeGPS(); + void InitializeCalibration(); + void InitializeSearch(); + void InitializeDisplay(); + + // System related functions + void OnClose(wxCloseEvent &event); + + // Status related functions + void OnHistogramSourceChg(wxCommandEvent &event); + void OnHistogramCoordsChg(wxCommandEvent &event); + + // Search related functions + void EnableSearchItems(bool enable); + void MakeNewLogAndSearch(); + void CalcSolution(); + void StartCaptureAutomatic(); + void StartCaptureOnce(); + void StopCapture(); + void UpdateSearchStatus(bool display); + void UpdateSearchLocation(bool display); + void UpdateSearchDirection(bool display); + void OnSearchNewSave(wxCommandEvent &event); + void OnSearchOpenClose(wxCommandEvent &event); + void OnSearchToggle(wxCommandEvent &event); + void OnSearchOnce(wxCommandEvent &event); + void OnSearchUpdate(SearchUpdate &event); + + // Doppler related functions + void SetDopplerParams(); + void StartDoppler(); + void StopDoppler(); + void UpdateDopplerStatus(bool display); + void OnDopplerToggle(wxCommandEvent &event); + void OnDopplerAutostart(wxCommandEvent &event); + void OnDopplerReset(wxCommandEvent &event); + void OnDopplerFilterChg(wxScrollEvent &event); + void OnDopplerRotationChg(wxCommandEvent &event); + void OnDopplerUpdate(EZDopplerUpdate &event); + + // GPS related functions + void StartGPS(); + void StopGPS(); + void UpdateGPSStatus(bool dipslay); + void UpdateGPSValidity(bool valid); + void OnGPSToggle(wxCommandEvent &event); + void OnGPSAutostart(wxCommandEvent &event); + void OnGPSDeviceSelect(wxCommandEvent &event); + void OnGPSUpdate(GPSUpdate &event); + + // Calibration related functions + void CalcKnownStatistics(); + void UpdateKnownLocation(); + void UpdateKnownDirection(); + void UpdateKnownStatistics(); + void OnCalibrationEqualize(wxCommandEvent &event); + void OnCalibrationZero(wxCommandEvent &event); + void OnCalibrationAdjustLeft(wxSpinEvent &event); + void OnCalibrationAdjustRight(wxSpinEvent &event); + void OnCalibrationAffectAllRates(wxCommandEvent &event); + void OnKnownTransmitterUpdate(wxCommandEvent &event); + void OnUseKnownTransmitter(wxCommandEvent &event); + + // Tactical display related functions + void OnDisplayOrientation(wxCommandEvent &event); + void OnDisplayDoppler(wxCommandEvent &event); + void OnDisplayKnown(wxCommandEvent &event); + void OnDisplayEstimated(wxCommandEvent &event); + + // Menu event handlers + void OnExit(wxCommandEvent &event); + void OnAbout(wxCommandEvent &event); + void OnFileNew(wxCommandEvent &event); + void OnFileOpen(wxCommandEvent &event); + void OnFileClose(wxCommandEvent &event); + void OnFileSave(wxCommandEvent &event); + void OnCalibrationSaveToFile(wxCommandEvent &event); + void OnCalibrationLoadFromFile(wxCommandEvent &event); + void OnCalibrationLoadTransmitter(wxCommandEvent &event); + void OnCalibrationSaveTransmitter(wxCommandEvent &event); + + // Member data + HunterSettings *m_settings; // Configuration file + EZDoppler *m_doppler; // Attached Doppler device + GPS *m_gps; // Attached GPS device + SampleLog *m_log; // Accumulated sample points + KnownTransmitter m_known; // Identified known transmitter location for calibration + TransmitterSearch m_search; // Search being conducted + + bool m_doppler_started; // Tracks start/stop of doppler device + bool m_gps_started; // Tracks start/stop of GPS device + bool m_capture; // Tracks start/stop of bearing capture + bool m_one_shot; // Tracks whether capture is one bearing only + int m_histogram_source; // Tracks histogram data source (0=estimated, 1=actual); + + Sample m_sample; // Current sample being updated by doppler and GPS + + // Child windows that need remembering + TacticalPanel *m_tactical_panel; + HistogramPanel *m_error_histogram_panel; + + // This class handles events + DECLARE_EVENT_TABLE(); +}; diff --git a/ezdop/src/host/hunter/src/hunter.xpm b/ezdop/src/host/hunter/src/hunter.xpm new file mode 100644 index 000000000..4d2ec23cd --- /dev/null +++ b/ezdop/src/host/hunter/src/hunter.xpm @@ -0,0 +1,39 @@ +/* XPM */ +static char * hunter_xpm[] = { +"32 32 4 1", +" c #000000", +". c #FF0000", +"+ c #00FF80", +"@ c #00FF00", +" ..... ", +" ............. ", +" ..... ...... ", +" .... + ... ", +" ... @+@ ... ", +" ... @+@ ... ", +" ... @+@ ... ", +" .. @+@ .. ", +" .. @+@ .. ", +" ... @+@ ... ", +" .. @+@ .. ", +" .. @+@ .. ", +" .. @+@ .. ", +".. @+@ .. ", +".. @@@ .. ", +".. @@@ .. ", +".. @@@ .. ", +".. .. ", +" .. .. ", +" .. .. ", +" .. .. ", +" ... ... ", +" .. .. ", +" .. .. ", +" ... ... ", +" ... ... ", +" ... ... ", +" .... .... ", +" ..... ..... ", +" ............. ", +" ..... ", +" "}; diff --git a/ezdop/src/host/hunter/src/hunter.xrc b/ezdop/src/host/hunter/src/hunter.xrc new file mode 100644 index 000000000..078616492 --- /dev/null +++ b/ezdop/src/host/hunter/src/hunter.xrc @@ -0,0 +1,1020 @@ + + + + + + + + + Create a new transmitter search. + + + + Open an existing search + + + + Close current search. + + + + + Save current search to a file. + + + + + Exit this application + + + + + + + + Start Doppler Rotation + + + + 1 + Automatically start Doppler when application loads + + + + Reset Doppler + + + + + + + + Load system calibration values from filesystem + + + + Save system calibration values to filesystem + + + + + Load transmitter coordinates from file + + + + Save transmitter coordinates to file + + + + + + + + About this application + + + + + + AE6HO Radio Location System + + + horizontal + 170 + + + + vertical + 240 + 240 + + + + + + + + + + + + + wxVERTICAL + + wxGROW|wxALL + 5 + + + wxVERTICAL + + wxGROW|wxLEFT|wxRIGHT|wxBOTTOM + 5 + + wxVERTICAL + + + wxGROW + + + + + + + wxGROW|wxLEFT|wxRIGHT|wxBOTTOM + 5 + + wxVERTICAL + + + wxGROW + + + + + + + wxGROW + 5 + + wxHORIZONTAL + + wxGROW|wxLEFT|wxRIGHT|wxBOTTOM + 5 + + + wxVERTICAL + + + wxGROW + + + + + + + + wxGROW|wxLEFT|wxRIGHT|wxBOTTOM + 5 + + + wxVERTICAL + + + wxGROW + + + + + + + + + + + + + wxGROW|wxALL + 5 + + + wxVERTICAL + + wxGROW|wxLEFT|wxRIGHT|wxBOTTOM + 5 + + + wxHORIZONTAL + + wxGROW|wxRIGHT + 5 + + + wxVERTICAL + + + wxGROW + + + + + + + + wxGROW|wxLEFT + 5 + + + wxVERTICAL + + + wxGROW + + + + + + + + + + wxGROW|wxLEFT|wxRIGHT|wxBOTTOM + 5 + + + wxHORIZONTAL + + wxGROW|wxRIGHT + 5 + + + wxVERTICAL + + + wxGROW + + + + + + + + wxGROW|wxLEFT + 5 + + + wxVERTICAL + + + wxGROW + + + + + + + + + + + + + wxGROW|wxALL + 5 + + + wxVERTICAL + + + #000000 + 220,220 + + + + + + + wxALL + 5 + + + + 1 + 0 + Estimated + Actual + + + + wxALL + 5 + + + + 1 + 0 + Rectangular + Polar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wxHORIZONTAL + + wxTOP + 5 + + wxVERTICAL + + wxLEFT|wxTOP|wxRIGHT + 5 + + + + + + wxLEFT|wxTOP|wxRIGHT + 5 + + + + + + wxLEFT|wxTOP|wxRIGHT + 5 + + + + + + + + wxTOP|wxRIGHT + 5 + + + wxVERTICAL + + wxALL|wxGROW + 5 + + + 250,45 + 1 + 20 + 100 + + + + + + wxTOP + 5 + + + + 2 + 5 + 250 + 400 + 500 + 666 + 1000 + 2000 + + + + + + + + + wxHORIZONTAL + + wxTOP + 5 + + wxVERTICAL + + wxALL + 5 + + + + + + wxALL + 5 + + + + + + + + wxTOP|wxRIGHT + 5 + + + wxVERTICAL + + wxALL|wxGROW + 5 + + 150,30 + + + + + + wxTOP|wxRIGHT + 5 + + wxVERTICAL + + + + wxVERTICAL + + + + 140,22 + + + + + + + + wxVERTICAL + + + + 140,22 + + + + + + + + + wxTOP|wxRIGHT + 5 + + wxVERTICAL + + + + wxVERTICAL + + + + 55,22 + + + + + + + + wxVERTICAL + + + + 55,22 + + + + + + + + + + + + + + wxHORIZONTAL + + wxTOP|wxRIGHT + 5 + + wxVERTICAL + + + + wxHORIZONTAL + + wxALL + 5 + + + 0 + + + + wxALL + 5 + + + 0 + + + + + + + + wxHORIZONTAL + + wxALL + 5 + + + + + + wxALL + 5 + + + 500 + 0 + 1000 + + + + + + + wxALL + 5 + + + + + + + + + + wxTOP|wxRIGHT + 5 + + wxHORIZONTAL + + + + wxVERTICAL + + wxRIGHT + 5 + + wxVERTICAL + + + + + + + + + + wxTOP||wxBOTTOM|wxALIGN_CENTRE + 5 + + + + + + + + + wxVERTICAL + + + wxVERTICAL + + + + + + + + + + wxTOP|wxALIGN_CENTRE + 10 + + + 0 + + + + + + + + wxTOP|wxRIGHT + 5 + + + wxHORIZONTAL + + + wxVERTICAL + + wxRIGHT + 5 + + + + + 75,22 + + + + + + + wxRIGHT + 5 + + + + + 75,22 + + + + + + + + + + wxVERTICAL + + + + + + 75,22 + + + + + + + + + + + 75,22 + + + + + + + + + + + + + + + + wxHORIZONTAL + + + + + + + + + + + wxHORIZONTAL + + + + + + + + + + + Equalize Doppler Rotation Rates + + wxVERTICAL + + wxALL + 10 + + + + + + wxGROW|wxALL + 5 + + wxHORIZONTAL + + + + + + 6 + + + + + + wxALL + 10 + + + + + + wxGROW|wxALL + 5 + + wxHORIZONTAL + + wxALIGN_LEFT + + + + + + 5 + + + + wxALIGN_RIGHT + + + + + + + + + + + + + + wxHORIZONTAL + + wxTOP|wxRIGHT + 5 + + + + 1 + 0 + Track Up + North Up + + + + wxTOP|wxRIGHT + 5 + + + wxVERTICAL + + + + + + + + + + + + + + + + + + + + + + + + + + + wxHORIZONTAL + + wxTOP|wxRIGHT + 5 + + + wxVERTICAL + + wxLEFT|wxTOP|wxRIGHT + 5 + + + + + + wxALL + 5 + + + + + + + + + wxTOP|wxRIGHT + 5 + + + wxVERTICAL + + wxTOP|wxLEFT|wxRIGHT + 5 + + + + + + wxALL + 5 + + + + + + + + + wxTOP|wxRIGHT + 5 + + + wxVERTICAL + + + + + + 150,22 + + + + + + + + + + + 150,22 + + + + + + + + + + wxTOP|wxRIGHT + 5 + + + wxHORIZONTAL + + + wxVERTICAL + + wxRIGHT + 5 + + + + + 100,22 + + + + + + + wxRIGHT + 5 + + + + + 100,22 + + + + + + + + + + + wxVERTICAL + + wxRIGHT + 5 + + + + + 100,22 + + + + + + + wxRIGHT + 5 + + + + + 100,22 + + + + + + + + + + + wxVERTICAL + + + + + + 100,22 + + + + + + + + + + + 100,22 + + + + + + + + + + + + + diff --git a/ezdop/src/host/hunter/src/hunterapp.cc b/ezdop/src/host/hunter/src/hunterapp.cc new file mode 100644 index 000000000..57113bf99 --- /dev/null +++ b/ezdop/src/host/hunter/src/hunterapp.cc @@ -0,0 +1,52 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "hunterapp.h" +#include "hunter.h" + +// wxWidgets includes +#include +#include + +// Provided in resource.cpp created from hunter.xrc by make system +extern void InitXmlResource(); + +bool Hunter::OnInit() +{ + m_logger = new wxLogStream(); + wxLog::SetActiveTarget(m_logger); + + // Get XML resources linked in via resource.cpp + wxXmlResource::Get()->InitAllHandlers(); + InitXmlResource(); + + HunterFrame *top = new HunterFrame(); + top->Show(true); + + // Everything Zen + return true; +} + +int Hunter::OnExit() +{ + return 0; +} + +// Creates main() and WinMain() entry points +IMPLEMENT_APP(Hunter) diff --git a/ezdop/src/host/hunter/src/hunterapp.h b/ezdop/src/host/hunter/src/hunterapp.h new file mode 100644 index 000000000..a71fc5704 --- /dev/null +++ b/ezdop/src/host/hunter/src/hunterapp.h @@ -0,0 +1,43 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __HUNTER_APP__ +#define __HUNTER_APP__ + +// wxWidgets includes +#include + +// Forward declarations +class wxLog; + +class Hunter : public wxApp +{ +public: + // Called on application startup + virtual bool OnInit(); + + // Called by system when application is closing but + // before wxWidgets is finished + virtual int OnExit(); + +private: + // Active log target + wxLog *m_logger; +}; + +#endif diff --git a/ezdop/src/host/hunter/src/known.cc b/ezdop/src/host/hunter/src/known.cc new file mode 100644 index 000000000..ed257e014 --- /dev/null +++ b/ezdop/src/host/hunter/src/known.cc @@ -0,0 +1,55 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "known.h" +#include "util.h" + +// wxWidget includes +#include + +using namespace std; + +KnownTransmitter::KnownTransmitter() +{ + m_valid = false; + ClearStats(); +} + +void KnownTransmitter::Location(const Spherical &location) +{ + m_location = location; + m_valid = true; + ClearStats(); +} + +void KnownTransmitter::Clear() +{ + m_valid = false; + ClearStats(); +} + +void KnownTransmitter::ClearStats() +{ + m_histogram.Reset(); +} + +void KnownTransmitter::Calc(const std::vector &samples) +{ + m_histogram.Calc(samples, m_location); +} diff --git a/ezdop/src/host/hunter/src/known.h b/ezdop/src/host/hunter/src/known.h new file mode 100644 index 000000000..6f1e6bff8 --- /dev/null +++ b/ezdop/src/host/hunter/src/known.h @@ -0,0 +1,59 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __KNOWN_H__ +#define __KNOWN_H__ + +// Application level includes +#include "spherical.h" +#include "sample.h" +#include "histogram.h" + +// System level includes +#include + +class KnownTransmitter +{ +public: + KnownTransmitter(); + + void Location(const Spherical &location); + Spherical Location() const { return m_location; } + + void Clear(); + void ClearStats(); + void Calc(const std::vector &samples); + + int Count() const { return m_histogram.Count(); } + int Mode() const { return m_histogram.Mode(); } + float Mean() const { return m_histogram.Mean(); } + float Concentration() const { return m_histogram.Concentration(); } + + bool IsSet() const { return m_valid; } + bool HasStats() const { return m_histogram.Count() > 0; } + double Latitude() const { return m_location.Latitude(); } + double Longitude() const { return m_location.Longitude(); } + const ErrorHistogram &Histogram() { return m_histogram; } + +private: + bool m_valid; + Spherical m_location; + ErrorHistogram m_histogram; +}; + +#endif diff --git a/ezdop/src/host/hunter/src/sample.cc b/ezdop/src/host/hunter/src/sample.cc new file mode 100644 index 000000000..eedf5c74c --- /dev/null +++ b/ezdop/src/host/hunter/src/sample.cc @@ -0,0 +1,139 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "sample.h" +#include "util.h" + +// wxWidgets includes +#include + +Sample::Sample() +{ + m_time = 0; + m_valid = false; + m_location = Spherical(0.0, 0.0); + m_heading = 0.0; + m_speed = 0.0; + m_in_phase = 0.0; + m_quadrature = 0.0; + m_phase = 0.0; + m_strength = 0.0; + m_volume = 0.0; + m_rate = 0; + m_filtering = 1; + m_error = 0.0; + m_ierror = 0.0; + m_qerror = 0.0; +} + +Sample::Sample(const Sample &sample) +{ + m_time = sample.m_time; + m_valid = sample.m_valid; + m_location = sample.m_location; + m_heading = sample.m_heading; + m_speed = sample.m_speed; + m_in_phase = sample.m_in_phase; + m_quadrature = sample.m_quadrature; + m_phase = sample.m_phase; + m_strength = sample.m_strength; + m_volume = sample.m_volume; + m_rate = sample.m_rate; + m_filtering = sample.m_filtering; + m_error = sample.m_error; + m_ierror = sample.m_ierror; + m_qerror = sample.m_qerror; +} + +Sample::Sample(wxString &line) +{ + char valid; + double lat, lon; + + sscanf((char *)line.c_str(), "%c %i %lf %lf %f %f %f %f %f %f %f %f %f %f %i %i", + &valid, + &m_time, + &lat, + &lon, + &m_heading, + &m_speed, + &m_volume, + &m_strength, + &m_in_phase, + &m_quadrature, + &m_phase, + &m_error, + &m_ierror, + &m_qerror, + &m_rate, + &m_filtering); + + if (valid == 'V') + m_valid = true; + else + m_valid = false; + + m_location = Spherical(lat, lon); +} + +void Sample::Dump(char *str, int len) +{ + // vld tim lat lon hdg speed vol stren inphs quad phase error ierr qerr rt flt + snprintf(str, len, "%s %10i %10.5lf %10.5lf %5.1f %6.2f %7.5f %7.5f %9.6f %9.6f %9.6f %7.2f %9.6f %9.6f %i %i", + m_valid ? "V":"I", + m_time, + m_location.Latitude(), + m_location.Longitude(), + m_heading, + m_speed, + m_volume, + m_strength, + m_in_phase, + m_quadrature, + m_phase, + m_error, + m_ierror, + m_qerror, + m_rate, + m_filtering); +} + +void Sample::CalcError(const Spherical &location, float &angle, float &ierror, float &qerror) const +{ + float actual_bearing = bearing(m_location, location); + float sample_relative_bearing = degree_normalize(to_degrees(m_phase)); + float sample_absolute_bearing = degree_normalize(sample_relative_bearing+m_heading); + angle = sample_absolute_bearing-actual_bearing; + + if (angle < -180.0) + angle += 360; + if (angle > 180.0) + angle -= 360; + + ierror = 0.0; + qerror = 0.0; + + // Rotate I, Q by actual bearing + float i_act = cos(to_radians(-actual_bearing)); + float q_act = sin(to_radians(-actual_bearing)); + ierror = m_in_phase*i_act - m_quadrature*q_act; + qerror = m_quadrature*i_act + m_in_phase*q_act; + + +} diff --git a/ezdop/src/host/hunter/src/sample.h b/ezdop/src/host/hunter/src/sample.h new file mode 100644 index 000000000..9ecce4857 --- /dev/null +++ b/ezdop/src/host/hunter/src/sample.h @@ -0,0 +1,97 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __SAMPLE_H__ +#define __SAMPLE_H__ + +// Application level includes +#include "spherical.h" + +// wxWidgets includes +#include + +// System level includes +#include + +class Sample +{ +public: + Sample(); + Sample(const Sample &sample); + Sample(wxString &line); + + void Time(time_t time) { m_time = time; } + void Valid(bool valid) { m_valid = valid; } + void Location(Spherical &location) { m_location = location; } + void Heading(float heading) { m_heading = heading; } + void Speed(float speed) { m_speed = speed; } + void InPhase(float in_phase) { m_in_phase = in_phase; } + void Quadrature(float quadrature) { m_quadrature = quadrature; } + void Phase(float phase) { m_phase = phase; } + void Strength(float strength) { m_strength = strength; } + void Volume(float volume) { m_volume = volume; } + void Rate(int rate) { m_rate = rate; } + void Filtering(int filtering) { m_filtering = filtering; } + void Error(float error) { m_error = error; } + void IError(float error) { m_ierror = error; } + void QError(float error) { m_qerror = error; } + + const Spherical &Location() const { return m_location; } + float Latitude() const { return m_location.Latitude(); } + float Longitude() const { return m_location.Longitude(); } + float Heading() const { return m_heading; } + float Speed() const { return m_speed; } + float InPhase() const { return m_in_phase; } + float Quadrature() const { return m_quadrature; } + float Phase() const { return m_phase; } + float Strength() const { return m_strength; } + float Volume() const { return m_volume; } + int Filtering() const { return m_filtering; } + float Error() const { return m_error; } + float IError() const { return m_ierror; } + float QError() const { return m_qerror; } + bool Valid() const { return m_valid; } + + void CalcError(const Spherical &location, float &error, float &ierror, float &qerror) const; + + void Dump(char *str, int len); // TEMPORARY + + operator const std::string(); // Conversion operator to std::string + +private: + // Data supplied by measuring system + time_t m_time; // Unix time of observation + bool m_valid; // GPS validity indication (NMEA "I" or "V") + Spherical m_location; // GPS latitude and longitude + float m_heading; // GPS heading in degrees 0.0 - 360.0 + float m_speed; // GPS speed in mph + float m_in_phase; // Doppler I channel -1.0 to 1.0 + float m_quadrature; // Doppler Q channel -1.0 to 1.0 + float m_phase; // Doppler phase -M_PI to M_PI (derived) + float m_strength; // Doppler strength 0.0 - 1.0 (derived) + float m_volume; // Doppler volume 0.0 - 1.0 + int m_rate; // Doppler rotation rate 0 - 5 + int m_filtering; // Doppler filtering 1 - 100 + + // Container configured + float m_error; // Known transmitter bearing error + float m_ierror; // Known transmitter in phase error + float m_qerror; // Known transmitter quadrature error +}; + +#endif diff --git a/ezdop/src/host/hunter/src/samplelog.cc b/ezdop/src/host/hunter/src/samplelog.cc new file mode 100644 index 000000000..449d1bd89 --- /dev/null +++ b/ezdop/src/host/hunter/src/samplelog.cc @@ -0,0 +1,107 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "samplelog.h" + +// wxWidgets includes +#include +#include + +// System level includes +#include + +SampleLog::SampleLog() +{ + m_file = NULL; + m_save = -1; +} + +void SampleLog::Load(wxString filename) +{ + m_file = new wxFile(filename.c_str(), wxFile::read_write); + wxString line; + + if (m_file && m_file->IsOpened()) { + while (readline(line)) { + Sample sample(line); // can't use inline temporary in next line + Add(sample); // Locking is handled in Add + } + + m_save = -1; + } +} + +bool SampleLog::readline(wxString &line) +{ + char ch; + size_t count; + + line.Empty(); + count = m_file->Read(&ch, 1); + while (count == 1 && ch != '\n') { + line.Append(ch); + count = m_file->Read(&ch, 1); + } + + return !line.IsEmpty(); +} + +void SampleLog::Add(Sample &sample) +{ + wxMutexLocker locker(m_mutex); + + if (m_save < 0) + m_save = m_samples.size(); + + m_samples.push_back(sample); + return; +} + +bool SampleLog::Save(wxString &filename) +{ + wxASSERT(!m_file); // Save called with filename when it already exists is an error + wxLogError(_T("SampleLog::Save: called with %s when file already exists"), filename.c_str()); + + m_filename = filename; + m_file = new wxFile(m_filename.c_str(), wxFile::write); + + return Save(); +} + +bool SampleLog::Save() +{ + wxASSERT(m_file); + if (m_save < 0) + return false; + + wxMutexLocker locker(m_mutex); + + char str[256]; + if (m_file && m_file->IsOpened()) { + for (int i = m_save; i < m_samples.size(); i++) { + m_samples[i].Dump(str, 255); + m_file->Write(str, strlen(str)); + m_file->Write("\n", strlen("\n")); + } + m_save = -1; + return true; + } + + return false; +} diff --git a/ezdop/src/host/hunter/src/samplelog.h b/ezdop/src/host/hunter/src/samplelog.h new file mode 100644 index 000000000..71e049fc2 --- /dev/null +++ b/ezdop/src/host/hunter/src/samplelog.h @@ -0,0 +1,63 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __SAMPLELOG_H__ +#define __SAMPLELOG_H__ + +// Application level includes +#include "sample.h" + +// wxWidgets includes +#include +#include + +// System level includes +#include + +// Forward declarations +class wxFile; + +class SampleLog +{ +public: + SampleLog(); + + // Sample access + void Add(Sample &sample); + int Count() const { return m_samples.size(); } + wxMutex &Mutex() { return m_mutex; } + std::vector& Samples() { return m_samples; } + + // File operations + void Load(wxString filename); + bool Save(); + bool Save(wxString &filename); + bool HasFile() const { return (m_file != NULL); } + bool IsDirty() const { return m_save >= 0; } + +private: + bool readline(wxString &line); + + std::vector m_samples; + wxMutex m_mutex; + int m_save; + wxString m_filename; + wxFile *m_file; +}; + +#endif diff --git a/ezdop/src/host/hunter/src/search.cc b/ezdop/src/host/hunter/src/search.cc new file mode 100644 index 000000000..9992cb896 --- /dev/null +++ b/ezdop/src/host/hunter/src/search.cc @@ -0,0 +1,211 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "search.h" +#include "sample.h" +#include "util.h" + +// wxWidgets includes +#include +#include + +// System level includes +#include + +using namespace std; + +const wxEventType wxEVT_SEARCH_UPDATE = wxNewEventType(); + +SearchUpdate::SearchUpdate(const wxEventType &event, bool done) : +wxNotifyEvent(event) +{ + m_done = done; +} + +TransmitterSearch::TransmitterSearch(wxEvtHandler *dest) : +m_condition(m_mutex) +{ + Reset(); + m_resolution = 0.0005; // 182 foot granularity + m_dest = dest; + m_log = NULL; + m_scale = 0.85; // Estimated from Mt. Umunhum data + m_busy = false; + Create(); // wxThreadHelper + GetThread()->Run(); +} + +TransmitterSearch::~TransmitterSearch() +{ + GetThread()->Delete(); +} + +void TransmitterSearch::Reset() +{ + m_iterations = 0; + m_initialized = false; + m_solution = Spherical(0.0, 0.0); + m_log = NULL; + ClearStats(); +} + +void TransmitterSearch::ClearStats() +{ + m_histogram.Reset(); +} + +void *TransmitterSearch::Entry() +{ + wxLogDebug(_T("TransmitterSearch::Entry(): entered")); + m_mutex.Lock(); + + while (!GetThread()->TestDestroy()) + if (m_condition.WaitTimeout(1000) == wxCOND_NO_ERROR) + background_solve(); + + m_mutex.Unlock(); + wxLogDebug(_T("TransmitterSearch::Entry(): exited")); +} + +void TransmitterSearch::background_solve() +{ + if (!m_log) + return; + + m_iterations = 0; + int count = m_log->Count(); + ClearStats(); + + m_busy = true; + if (count == 0) // No data to solve from + return post_update(true); + + if (!m_initialized) { // Get initial solution from first sample, but only once + m_solution = m_log->Samples().begin()->Location(); + m_initialized = true; + } + + if (count == 1) + return post_update(true); + + while(1) { + m_iterations = 0; + while (++m_iterations <= MaxIterations) { + wxMutexLocker locker(m_log->Mutex()); + if (hillclimb(m_log->Samples())) { // true return indicates solution of some sort achieved + m_histogram.Calc(m_log->Samples(), m_solution); + return post_update(true); + } + } + + // Max iterations reached, send interim solution + m_histogram.Calc(m_log->Samples(), m_solution); + post_update(false); + } +} + +void TransmitterSearch::post_update(bool done) +{ + SearchUpdate update(wxEVT_SEARCH_UPDATE, done); + wxPostEvent(m_dest, update); + m_busy = !done; +} + +void TransmitterSearch::Solve(SampleLog *log) +{ + // FIXME: what if we get here while background thread is still busy? + if (m_log && (m_log != log)) + wxLogError(_T("TransmitterSearch::Solve: supplied log different from current one.")); + + m_log = log; + m_condition.Signal(); +} + +// Return value indicates solution of some sort achieved +bool TransmitterSearch::hillclimb(vector &samples) +{ + int nx, ny; + int min_x = 0, min_y = 0; + int num; + + Spherical trial; + + float min_error; + float trial_error; + + // Initialize search with current solution + if (calc_trial_error(samples, m_solution, min_error) == 0.0) { + wxLogDebug(_T("TransmitterSearch::hillclimb: no enabled samples, returning")); + return true; // No enabled data points, we're done + } + + // Check if moving 'resolution' distance in one of four directions decreases error + for (nx = -1; nx < 2; nx++) { + trial.SetLongitude(m_solution.Longitude() + nx*m_resolution); + for (ny = -1; ny < 2; ny++) { + // Skip non-compass directions + if (nx == ny) + continue; + trial.SetLatitude(m_solution.Latitude() + ny*m_resolution); + calc_trial_error(samples, trial, trial_error); + if (trial_error < min_error) { + min_error = trial_error; + min_x = nx; min_y = ny; + } + } + } + + // Indicate if solution achieved + if (min_x == 0 && min_y == 0) + return true; + else { + m_solution.SetLatitude(m_solution.Latitude()+min_y*m_resolution); + m_solution.SetLongitude(m_solution.Longitude()+min_x*m_resolution); + return false; // Make outer loop call us again + } +} + +// Return value is number of enabled samples in vector +float TransmitterSearch::calc_trial_error(const vector&samples, + const Spherical &trial, + float &trial_error) +{ + float wsum = 0.0; + trial_error = 0.0; + float strength = 1.0; + + for (int i = 0; i < samples.size(); i++) { + const Sample &sample = samples[i]; + + float angle, ierror, qerror; + sample.CalcError(trial, angle, ierror, qerror); + + // Wrapped cauchy distribution + float p = m_scale; + float likelihood = (1-p*p)/(1+p*p-2*p*cos(angle*M_PI/180.0)); + + trial_error += -log(likelihood)*sample.Strength(); + wsum += sample.Strength(); + } + + if (wsum > 0.0) + trial_error = trial_error/wsum; + + return wsum; +} diff --git a/ezdop/src/host/hunter/src/search.h b/ezdop/src/host/hunter/src/search.h new file mode 100644 index 000000000..b0e35d004 --- /dev/null +++ b/ezdop/src/host/hunter/src/search.h @@ -0,0 +1,107 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __SEARCH_H__ +#define __SEARCH_H__ + +// Application level includes +#include "spherical.h" +#include "samplelog.h" +#include "histogram.h" + +// wxWidgets includes +#include + +// System level includes +#include + +// Forward declarations +class wxFile; +class Sample; +class TransmitterSearch; + +class SearchUpdate : public wxNotifyEvent +{ +public: + SearchUpdate(const wxEventType &event, bool done); + virtual wxEvent *Clone() const { return new SearchUpdate(*this); } + bool m_done; +}; + +extern const wxEventType wxEVT_SEARCH_UPDATE; + +typedef void(wxEvtHandler::*SearchUpdateFunction)(SearchUpdate&); + +#define EVT_SEARCH_UPDATE(fn) \ + DECLARE_EVENT_TABLE_ENTRY( \ + wxEVT_SEARCH_UPDATE, -1, -1, \ + (wxObjectEventFunction)(wxEventFunction)(SearchUpdateFunction)&fn, \ + (wxObject *)NULL \ + ), + +class TransmitterSearch : public wxThreadHelper +{ +public: + TransmitterSearch(wxEvtHandler *dest); + ~TransmitterSearch(); + + void Solve(SampleLog *log); + bool HasSolution() { return m_log ? m_log->Count() > 1 : false; } + void Reset(); + void ClearStats(); + bool Busy() const { return m_busy; } + int Mode() const { return m_histogram.Mode(); } + float Mean() const { return m_histogram.Mean(); } + float Concentration() { return m_histogram.Concentration(); } + + const ErrorHistogram &Histogram() { return m_histogram; } + Spherical GetEstimatedLocation() { return m_solution; } + + virtual void *Entry(); + +private: + static const int MaxIterations = 20; // TODO: make configurable + + bool hillclimb(std::vector &samples); + float calc_trial_error(const std::vector &samples, const Spherical &trial, + float &solution_error); + + void background_solve(); + void post_update(bool done); + + // Background processing + wxEvtHandler *m_dest; + wxMutex m_mutex; + wxCondition m_condition; + + // Solution state + SampleLog *m_log; + Spherical m_solution; + int m_iterations; + bool m_initialized; + bool m_busy; + + // Solution options + float m_scale; + float m_resolution; + + // Estimated solution histogram + ErrorHistogram m_histogram; +}; + +#endif diff --git a/ezdop/src/host/hunter/src/serial.cc b/ezdop/src/host/hunter/src/serial.cc new file mode 100644 index 000000000..5ace5aac1 --- /dev/null +++ b/ezdop/src/host/hunter/src/serial.cc @@ -0,0 +1,216 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include "serial.h" + +#include + +#ifdef __WIN32__ +// I hate Windows. +#else +#include +#include +#include +#endif + +wxArrayString EnumerateSerialPorts() +{ + wxArrayString result; + +#ifdef __WIN32__ + wxString port; + for (int i = 1; i <= 8; i++) { + port.Printf("COM%i", i); + result.Add(port); + } +#else + result.Add(_T("/dev/ttyS0")); + result.Add(_T("/dev/ttyS1")); + result.Add(_T("/dev/ttyS2")); + result.Add(_T("/dev/ttyS3")); + result.Add(_T("/dev/ttyUSB0")); + result.Add(_T("/dev/ttyUSB1")); +#endif + + return result; +} + +SerialPort::SerialPort(wxString &port) +{ + wxLogDebug(_T("SerialPort::SerialPort(): %s"), port.c_str()); + m_port = port; + m_opened = false; +#ifdef __WIN32__ + m_handle = INVALID_HANDLE_VALUE; +#else + m_fd = -1; +#endif +} + +bool SerialPort::Open(int speed) +{ + wxLogDebug(_T("SerialPort::Open: %i baud"), speed); + if (m_opened) { + wxLogWarning(_T("SerialPort::Open: called on already opened object.")); + return false; + } + +#ifdef __WIN32__ + m_handle = CreateFile(m_port.c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL); + if (m_handle == INVALID_HANDLE_VALUE) { + wxLogError("SerialPort::Open: CreateFile() failed"); + return false; + } + + DCB dcb; + if (!GetCommState(m_handle, &dcb)) { + wxLogError("SerialPort::Open: GetCommState failed."); + CloseHandle(m_handle); + return false; + } + + dcb.BaudRate = speed; + dcb.ByteSize = 8; + dcb.StopBits = ONESTOPBIT; + dcb.Parity = NOPARITY; + dcb.fBinary = TRUE; + dcb.fParity = FALSE; + + if (!SetCommState(m_handle, &dcb)) { + wxLogError("SerialPort::Open: SetCommState failed."); + CloseHandle(m_handle); + return false; + } + + COMMTIMEOUTS timeouts; + if (!GetCommTimeouts(m_handle, &timeouts)) { + wxLogError("SerialPort::Open: GetCommTimeouts failed."); + CloseHandle(m_handle); + return false; + } + + timeouts.ReadIntervalTimeout = MAXDWORD; + timeouts.ReadTotalTimeoutMultiplier = MAXDWORD; + timeouts.ReadTotalTimeoutConstant = 100; + + if (!SetCommTimeouts(m_handle, &timeouts)) { + wxLogError("SerialPort::Open: SetCommTimeouts failed."); + CloseHandle(m_handle); + return false; + } + + m_opened = true; +#else + m_fd = open((char *)m_port.c_str(), O_RDWR|O_NONBLOCK); + if (m_fd < 0) { + wxLogError(_T("SerialPort::Open: open() returned %i"), m_fd); + return false; + } + + if (!isatty(m_fd)) { + wxLogError(_T("SerialPort::Open: device %s is not a tty"), m_port.c_str()); + close(m_fd); + return false; + } + + if (tcgetattr(m_fd, &m_ttyset_old) != 0) { + wxLogError(_T("SerialPort::Open: failed to get port attributes")); + close(m_fd); + return false; + } + + memcpy(&m_ttyset_new, &m_ttyset_old, sizeof(m_ttyset_new)); + cfsetispeed(&m_ttyset_new, (speed_t)speed); + cfsetospeed(&m_ttyset_new, (speed_t)speed); + m_ttyset_new.c_cflag &= ~(PARENB|CRTSCTS); // Disable parity and flowcontrol + m_ttyset_new.c_cflag |= CS8|CREAD|CLOCAL; // 8 bits, read, no modem status lines + m_ttyset_new.c_iflag = 0; + m_ttyset_new.c_oflag = ONLCR; // Convert LF to CRLF on receive + m_ttyset_new.c_lflag = 0; + + if (tcsetattr(m_fd, TCSANOW, &m_ttyset_new) != 0) { + wxLogError(_T("SerialPort::Open: failed to set port attributes")); + close(m_fd); + return false; + } + + m_opened = true; +#endif + return m_opened; +} + +void SerialPort::Close() +{ + wxLogDebug(_T("SerialPort::Close()")); +#ifdef __WIN32__ + CloseHandle(m_handle); +#else + if (m_opened >= 0) { + m_ttyset_old.c_cflag |= HUPCL; + tcsetattr(m_fd, TCSANOW, &m_ttyset_old); + close(m_fd); + } +#endif + + m_opened = false; +} + +SerialPort::~SerialPort() +{ + wxLogDebug(_T("SerialPort::~SerialPort()")); + + if (m_opened) + Close(); +} + +int SerialPort::RxReady() +{ + int count = 0; +#ifdef __WIN32__ + return 1; // No equivalent Win32 call, use read timeouts instead +#else + if (m_fd < 0 || ioctl(m_fd, FIONREAD, &count) < 0) + return -1; +#endif + return count; +} + +int SerialPort::Read(char *buffer, int len) +{ + wxASSERT(buffer); + wxASSERT(len); + + if (!m_opened) + return -1; + +#ifdef __WIN32__ + DWORD num; + if (ReadFile(m_handle, buffer, (DWORD)len, &num, NULL)) + return num; + else + return -1; +#else + return read(m_fd, buffer, len); +#endif +} diff --git a/ezdop/src/host/hunter/src/serial.h b/ezdop/src/host/hunter/src/serial.h new file mode 100644 index 000000000..63bfe4327 --- /dev/null +++ b/ezdop/src/host/hunter/src/serial.h @@ -0,0 +1,56 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __SERIAL_H__ +#define __SERIAL_H__ + +#include + +#ifdef __WIN32__ +#include +#else +#include +#endif + +wxArrayString EnumerateSerialPorts(); + +class SerialPort +{ +public: + SerialPort(wxString &port); + ~SerialPort(); + + bool Open(int speed); + bool IsOpened() { return m_opened; } + void Close(); + int RxReady(); + int Read(char *buffer, int len); + +private: +#ifdef __WIN32__ + HANDLE m_handle; +#else + int m_fd; + struct termios m_ttyset_old; + struct termios m_ttyset_new; +#endif + wxString m_port; + bool m_opened; +}; + +#endif // __SERIAL_H__ diff --git a/ezdop/src/host/hunter/src/settings.cpp b/ezdop/src/host/hunter/src/settings.cpp new file mode 100644 index 000000000..646769fc9 --- /dev/null +++ b/ezdop/src/host/hunter/src/settings.cpp @@ -0,0 +1,310 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "settings.h" + +HunterSettings::HunterSettings() +{ + cfg = new wxConfig(_T("TransmitterHunter")); +} + +HunterSettings::~HunterSettings() +{ + if (cfg) { + cfg->Flush(); + delete cfg; + } +} + +wxSize HunterSettings::GetWindowSize() +{ + long width, height; + if (cfg) { + cfg->Read(wxT("Application/Width"), &width, 1024); + cfg->Read(wxT("Application/Height"), &height, 768); + } + + return wxSize((int)width, (int)height); +} + +void HunterSettings::SetWindowSize(wxSize size) +{ + if (cfg) { + cfg->Write(wxT("Application/Width"), size.GetWidth()); + cfg->Write(wxT("Application/Height"), size.GetHeight()); + } +} + +int HunterSettings::GetWindowXPos() +{ + long x; + if (cfg) + cfg->Read(wxT("Application/XPos"), &x, 0); + + return (int)x; +} + +void HunterSettings::SetWindowXPos(int x) +{ + if (cfg) + cfg->Write(wxT("Application/XPos"), (long)x); +} + +int HunterSettings::GetWindowYPos() +{ + long y; + if (cfg) + cfg->Read(wxT("Application/YPos"), &y, 0); + + return (int)y; +} + +void HunterSettings::SetWindowYPos(int y) +{ + if (cfg) + cfg->Write(wxT("Application/YPos"), (long)y); +} + +bool HunterSettings::GetDopplerAutostart() +{ + bool start = false; + if (cfg) + cfg->Read(wxT("Doppler/Autostart"), &start, false); + + return start; +} + +void HunterSettings::SetDopplerAutostart(bool start) +{ + if (cfg) + cfg->Write(wxT("Doppler/Autostart"), start); +} + +int HunterSettings::GetDopplerFilter() +{ + long filtering; + if (cfg) + cfg->Read(wxT("Doppler/FilterLevel"), &filtering, 20); + + return (int)filtering; +} + +void HunterSettings::SetDopplerFilter(int level) +{ + if (cfg) + cfg->Write(wxT("Doppler/FilterLevel"), (long)level); +} + +int HunterSettings::GetDopplerRotation() +{ + long rate; + if (cfg) + cfg->Read(wxT("Doppler/Rotation"), &rate, 4); + + return (int)rate; +} + +void HunterSettings::SetDopplerRotation(int rate) +{ + if (cfg) + cfg->Write(wxT("Doppler/Rotation"), (long)rate); +} + +float HunterSettings::GetDopplerCalibration(int rate) +{ + double calibration; + wxString key; + + key.Printf(_T("Doppler/Rate%iCalibration"), rate); + if (cfg) + cfg->Read(key, &calibration, 0.0); + return (float)calibration; +} + +void HunterSettings::SetDopplerCalibration(int rate, float offset) +{ + wxString key; + key.Printf(_T("Doppler/Rate%iCalibration"), rate); + if (cfg) + cfg->Write(key, offset); +} + +bool HunterSettings::GetGPSAutostart() +{ + bool start = false; + if (cfg) + cfg->Read(wxT("GPS/Autostart"), &start, false); + + return start; +} + +void HunterSettings::SetGPSAutostart(bool start) +{ + if (cfg) + cfg->Write(wxT("GPS/Autostart"), start); +} + +wxString HunterSettings::GetGPSDeviceName() +{ + wxString name; + if (cfg) + cfg->Read(wxT("GPS/DeviceName"), &name); + + return name; +} + +void HunterSettings::SetGPSDeviceName(wxString &name) +{ + if (cfg) + cfg->Write(wxT("GPS/DeviceName"), name); +} + +bool HunterSettings::GetCalibrationAffectAllRates() +{ + bool val = false; + if (cfg) + cfg->Read(wxT("Calibration/AffectAllRates"), &val, false); + + return val; +} + +void HunterSettings::SetCalibrationAffectAllRates(bool val) +{ + if (cfg) + cfg->Write(wxT("Calibration/AffectAllRates"), val); +} + +double HunterSettings::GetKnownTransmitterLongitude() +{ + double lon; + if (cfg) + cfg->Read(wxT("KnownTransmitter/Longitude"), &lon); + + return lon; +} + +void HunterSettings::SetKnownTransmitterLongitude(double lon) +{ + if (cfg) + cfg->Write(wxT("KnownTransmitter/Longitude"), lon); +} + +double HunterSettings::GetKnownTransmitterLatitude() +{ + double lat; + if (cfg) + cfg->Read(wxT("KnownTransmitter/Latitude"), &lat); + + return lat; +} + +void HunterSettings::SetKnownTransmitterLatitude(double lat) +{ + if (cfg) + cfg->Write(wxT("KnownTransmitter/Latitude"), lat); +} + +bool HunterSettings::GetUseKnownTransmitter() +{ + bool use = false; + if (cfg) + cfg->Read(wxT("KnownTransmitter/Use"), &use, false); + + return use; +} + +void HunterSettings::SetUseKnownTransmitter(bool use) +{ + if (cfg) + cfg->Write(wxT("KnownTransmitter/Use"), use); +} + +int HunterSettings::GetDisplayOrientation() +{ + long x; + if (cfg) + cfg->Read(wxT("Display/Orientation"), &x, 0); + + return (int)x; +} + +void HunterSettings::SetDisplayOrientation(int orientation) +{ + if (cfg) + cfg->Write(wxT("Display/Orientation"), (long)orientation); +} + +bool HunterSettings::GetDisplayDoppler() +{ + bool val = false; + if (cfg) + cfg->Read(wxT("Display/DopplerPointer"), &val, false); + + return val; +} + +void HunterSettings::SetDisplayDoppler(bool val) +{ + if (cfg) + cfg->Write(wxT("Display/DopplerPointer"), val); +} + +bool HunterSettings::GetDisplayKnown() +{ + bool val = false; + if (cfg) + cfg->Read(wxT("Display/KnownPointer"), &val, false); + + return val; +} + +void HunterSettings::SetDisplayKnown(bool val) +{ + if (cfg) + cfg->Write(wxT("Display/KnownPointer"), val); +} + +bool HunterSettings::GetDisplayEstimated() +{ + bool val = false; + if (cfg) + cfg->Read(wxT("Display/EstimatedPointer"), &val, false); + + return val; +} + +void HunterSettings::SetDisplayEstimated(bool val) +{ + if (cfg) + cfg->Write(wxT("Display/EstimatedPointer"), val); +} + +wxString HunterSettings::GetWorkingDirectory() +{ + wxString str(_T(".")); + if (cfg) + return cfg->Read(wxT("Application/WorkingDirectory"), str); + return str; +} + +void HunterSettings::SetWorkingDirectory(const wxString &dir) +{ + if (cfg) + cfg->Write(wxT("Application/WorkingDirectory"), dir); +} diff --git a/ezdop/src/host/hunter/src/settings.h b/ezdop/src/host/hunter/src/settings.h new file mode 100644 index 000000000..66e03775b --- /dev/null +++ b/ezdop/src/host/hunter/src/settings.h @@ -0,0 +1,101 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __SETTINGS_H__ +#define __SETTINGS_H__ + +// wxWidgets includes +#include +#include + +class HunterSettings +{ +public: + HunterSettings(); + ~HunterSettings(); + + // Main window size + wxSize GetWindowSize(); + void SetWindowSize(wxSize size); + + // Main window position + int GetWindowXPos(); + void SetWindowXPos(int x); + int GetWindowYPos(); + void SetWindowYPos(int y); + + // Autostart doppler on application bringup + bool GetDopplerAutostart(); + void SetDopplerAutostart(bool val); + + // Doppler filter value + int GetDopplerFilter(); + void SetDopplerFilter(int level); + + // Doppler filter value + int GetDopplerRotation(); + void SetDopplerRotation(int rate); + + // Doppler calibration values + float GetDopplerCalibration(int rate); + void SetDopplerCalibration(int rate, float offset); + + // Autostart GPS on application bringup + bool GetGPSAutostart(); + void SetGPSAutostart(bool val); + + // GPS interface device + wxString GetGPSDeviceName(); + void SetGPSDeviceName(wxString &name); + + // Calibration adjust affects all rates + bool GetCalibrationAffectAllRates(); + void SetCalibrationAffectAllRates(bool val); + + // Known transmitter location + double GetKnownTransmitterLongitude(); + void SetKnownTransmitterLongitude(double lon); + double GetKnownTransmitterLatitude(); + void SetKnownTransmitterLatitude(double lat); + bool GetUseKnownTransmitter(); + void SetUseKnownTransmitter(bool use); + + // Display Orientation + int GetDisplayOrientation(); + void SetDisplayOrientation(int orientation); + + // Display Doppler Bearing + bool GetDisplayDoppler(); + void SetDisplayDoppler(bool val); + + // Display Known Bearing + bool GetDisplayKnown(); + void SetDisplayKnown(bool val); + + // Display Estimated Bearing + bool GetDisplayEstimated(); + void SetDisplayEstimated(bool val); + + wxString GetWorkingDirectory(); + void SetWorkingDirectory(const wxString &dir); + +private: + wxConfig *cfg; +}; + +#endif // __SETTINGS_H__ diff --git a/ezdop/src/host/hunter/src/spherical.cc b/ezdop/src/host/hunter/src/spherical.cc new file mode 100644 index 000000000..2af34aa8e --- /dev/null +++ b/ezdop/src/host/hunter/src/spherical.cc @@ -0,0 +1,76 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "spherical.h" +#include "util.h" + +Spherical::Spherical() +{ + m_latitude = 0.0; + m_longitude = 0.0; +} + +Spherical::Spherical(double latitude, double longitude) +{ + m_latitude = latitude; + m_longitude = longitude; +} + +Spherical::Spherical(wxString latitude, wxString longitude) +{ + latitude.ToDouble(&m_latitude); + longitude.ToDouble(&m_longitude); +} + +void Spherical::SetLatitude(double latitude) +{ + m_latitude = latitude; // TODO: error handle +} + +void Spherical::SetLongitude(double longitude) +{ + m_longitude = longitude; +} + +double range(const Spherical &from, const Spherical &to) +{ + double lat1 = to_radians(from.m_latitude); + double lon1 = to_radians(from.m_longitude); + + double lat2 = to_radians(to.m_latitude); + double lon2 = to_radians(to.m_longitude); + + double n1 = sin((lat1-lat2)/2); + double n2 = sin((lon1-lon2)/2); + double distance = 2*asin(sqrt(n1*n1 + cos(lat1)*cos(lat2)*n2*n2)); + + return to_degrees(distance)*60.0*1.15077945; // Conversion to statute miles + +} + +double bearing(const Spherical &from, const Spherical &to) +{ + double lat1 = to_radians(from.m_latitude); + double lon1 = to_radians(from.m_longitude); + double lat2 = to_radians(to.m_latitude); + double lon2 = to_radians(to.m_longitude); + + double bearing = atan2(-sin(lon1-lon2)*cos(lat2), cos(lat1)*sin(lat2)-sin(lat1)*cos(lat2)*cos(lon1-lon2)); + return degree_normalize(to_degrees(bearing)); +} diff --git a/ezdop/src/host/hunter/src/spherical.h b/ezdop/src/host/hunter/src/spherical.h new file mode 100644 index 000000000..f450db769 --- /dev/null +++ b/ezdop/src/host/hunter/src/spherical.h @@ -0,0 +1,45 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __SPHERICAL_H__ +#define __SPHERICAL_H__ + +// wxWidgets includes +#include + +class Spherical +{ +public: + Spherical(); + Spherical(double latitude, double longitude); + Spherical(wxString latitude, wxString longitude); + + double Latitude() const { return m_latitude; } + double Longitude() const { return m_longitude; } + void SetLatitude(double latitude); + void SetLongitude(double longitude); + +private: + double m_latitude; + double m_longitude; + + friend double range(const Spherical &from, const Spherical &to); + friend double bearing(const Spherical &from, const Spherical &to); +}; + +#endif // __SPHERICAL_H__ diff --git a/ezdop/src/host/hunter/src/tactical.cc b/ezdop/src/host/hunter/src/tactical.cc new file mode 100644 index 000000000..4489357a9 --- /dev/null +++ b/ezdop/src/host/hunter/src/tactical.cc @@ -0,0 +1,142 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Application level includes +#include "tactical.h" + +// wxWidgets includes +#include +#include +#include + +// System level includes +#include + +// Event table for TacticalPanel +BEGIN_EVENT_TABLE(TacticalPanel, wxPanel) + EVT_PAINT(TacticalPanel::OnPaint) + EVT_SIZE(TacticalPanel::OnSize) +END_EVENT_TABLE() + +TacticalPanel::TacticalPanel(wxWindow *parent) : +wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) +{ + m_orientation = TrackUp; + m_display_doppler = false; + m_display_known = false; + m_display_estimated = false; + + m_heading = 0.0; + m_doppler_bearing = -1.0; + m_estimated_bearing = -1.0; + m_actual_bearing = -1.0; + + SetBackgroundColour(*wxBLACK); +} + +void TacticalPanel::OnPaint(wxPaintEvent &event) +{ + wxPaintDC dc(this); + drawPanel(dc); +} + +void TacticalPanel::drawPanel(wxDC &dc) +{ + float radians; + float brg = 0; + + // Draw circle + dc.SetPen(wxPen(*wxRED, 2, wxSOLID)); + dc.SetBrush(wxBrush(*wxBLACK, wxTRANSPARENT)); + dc.DrawCircle(m_center, m_radius); + + // Calculate end of doppler bearing line + // Doppler bearings are relative and must be adjusted for north up display + wxPoint doppler_tip = m_center; + if (m_doppler_bearing >= 0.0) { + brg = m_doppler_bearing; + if (m_orientation == NorthUp) { + brg += m_heading; + if (brg >= 360.0) + brg -= 360.0; + } + radians = brg*M_PI/180.0; + doppler_tip = wxPoint((int)(m_center.x+sin(radians)*m_radius*0.95), + (int)(m_height-(m_center.y+cos(radians)*m_radius*0.95))); + } + + // Calculate end of actual bearing line + // Actual bearings are absolute and must be adjusted for track up display + wxPoint actual_tip = m_center; + if (m_actual_bearing >= 0.0) { + brg = m_actual_bearing; + if (m_orientation == TrackUp) { + brg -= m_heading; + if (brg < 0.0) + brg += 360.0; + } + radians = brg*M_PI/180.0; + actual_tip = wxPoint((int)(m_center.x+sin(radians)*m_radius*0.95), + (int)(m_height-(m_center.y+cos(radians)*m_radius*0.95))); + } + + // Calculate end of estimated bearing line + // Estimated bearings are absolute and must be adjusted for track up display + wxPoint estimated_tip = m_center; + if (m_estimated_bearing >= 0.0) { + brg = m_estimated_bearing; + if (m_orientation == TrackUp) { + brg -= m_heading; + if (brg < 0.0) + brg += 360.0; + } + radians = brg*M_PI/180.0; + estimated_tip = wxPoint((int)(m_center.x+sin(radians)*m_radius*0.95), + (int)(m_height-(m_center.y+cos(radians)*m_radius*0.95))); + } + + if (m_display_known) { + dc.SetPen(wxPen(*wxBLUE, 10, wxSOLID)); + dc.DrawLine(m_center, actual_tip); + } + + if (m_display_estimated) { + dc.SetPen(wxPen(*wxWHITE, 10, wxSOLID)); + dc.DrawLine(m_center, estimated_tip); + } + + if (m_display_doppler) { + dc.SetPen(wxPen(*wxGREEN, 10, wxSOLID)); + dc.DrawLine(m_center, doppler_tip); + } +} + +void TacticalPanel::OnSize(wxSizeEvent &event) +{ + GetClientSize(&m_width, &m_height); + m_center = wxPoint(m_width/2, m_height/2); + + // Circle at 95% of window size + if (m_width > m_height) + m_radius = m_height/2; + else + m_radius = m_width/2; + m_radius = (int)(m_radius*0.95); + + Refresh(); +} diff --git a/ezdop/src/host/hunter/src/tactical.h b/ezdop/src/host/hunter/src/tactical.h new file mode 100644 index 000000000..4c26495b5 --- /dev/null +++ b/ezdop/src/host/hunter/src/tactical.h @@ -0,0 +1,73 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __TACTICAL_H__ +#define __TACTICAL_H__ + +// wxWidgets includes +#include + +enum Orientation { + TrackUp, + NorthUp +}; + +class TacticalPanel : public wxPanel +{ +public: + TacticalPanel(wxWindow *parent); + + // Event handlers + void OnPaint(wxPaintEvent &event); + void OnSize(wxSizeEvent &event); + + // Configuration + void SetOrientation(Orientation orientation) { m_orientation = orientation; Refresh(); } + void SetDisplayDoppler(bool display) { m_display_doppler = display; Refresh(); } + void SetDisplayKnown(bool display) { m_display_known = display; Refresh(); } + void SetDisplayEstimated(bool display) { m_display_estimated = display; Refresh(); } + + // State updates + void SetHeading(float heading) { m_heading = heading; Refresh(); } + void SetDopplerBearing(float bearing) { m_doppler_bearing = bearing; Refresh(); } + void SetEstimatedBearing(float bearing) { m_estimated_bearing = bearing; Refresh(); } + void SetActualBearing(float bearing) { m_actual_bearing = bearing; Refresh(); } + +private: + Orientation m_orientation; + bool m_display_doppler; + bool m_display_known; + bool m_display_estimated; + + float m_heading; + float m_doppler_bearing; + float m_estimated_bearing; + float m_actual_bearing; + + void drawPanel(wxDC &dc); + + // Window size derived parameters + wxPoint m_center; + int m_width; + int m_height; + int m_radius; + + DECLARE_EVENT_TABLE(); +}; + +#endif // __TACTICAL_H__ diff --git a/ezdop/src/host/hunter/src/util.h b/ezdop/src/host/hunter/src/util.h new file mode 100644 index 000000000..53e0f39d6 --- /dev/null +++ b/ezdop/src/host/hunter/src/util.h @@ -0,0 +1,79 @@ +/* + Copyright 2006 Johnathan Corgan. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This software 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 GNU Radio; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef __UTIL_H__ +#define __UTIL_H__ + +// System level includes +#include + +inline double limit(double x, double lower, double upper) +{ + if (x < lower) + return lower; + else if (x > upper) + return upper; + else + return x; +} + +inline int limit(int x, int lower, int upper) +{ + if (x < lower) + return lower; + else if (x > upper) + return upper; + else + return x; +} + +inline double degree_normalize(double degrees) +{ + if (degrees >= 360.0) + return degrees - 360.0; + else if (degrees < 0.0) + return degrees + 360.0; + else + return degrees; +} + +inline int degree_normalize(int degrees) +{ + if (degrees >= 360) + return degrees - 360; + else if (degrees < 0) + return degrees + 360; + else + return degrees; +} + +inline double to_radians(double degrees) +{ + return degrees/180.0*M_PI; +} + +inline double to_degrees(double radians) +{ + return radians/M_PI*180.0; +} + +#define LOGFUNCTION wxLogDebug("%s", __PRETTY_FUNCTION__) +#define LOGFUNCTIONENTRY wxLogDebug("%s: entered", __PRETTY_FUNCTION__) +#define LOGFUNCTIONEXIT wxLogDebug("%s: exited", __PRETTY_FUNCTION__) + +#endif // __UTIL_H__ -- cgit