diff options
author | jcorgan | 2006-08-12 23:25:38 +0000 |
---|---|---|
committer | jcorgan | 2006-08-12 23:25:38 +0000 |
commit | 676acd5cc47828bc92c2fad1e474a39aacbd2ba4 (patch) | |
tree | 55a35ee87ae50ea6acc113b20ec28045ca0c9b5f /ezdop | |
parent | 0b9b3564fd50c630fbbce7a06fda134ae5468828 (diff) | |
download | gnuradio-676acd5cc47828bc92c2fad1e474a39aacbd2ba4.tar.gz gnuradio-676acd5cc47828bc92c2fad1e474a39aacbd2ba4.tar.bz2 gnuradio-676acd5cc47828bc92c2fad1e474a39aacbd2ba4.zip |
Merged jcorgan/ezdop into trunk.
git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@3264 221aa14e-8319-0410-a670-987f0aec2ac5
Diffstat (limited to 'ezdop')
63 files changed, 7660 insertions, 0 deletions
diff --git a/ezdop/AUTHORS b/ezdop/AUTHORS new file mode 100644 index 000000000..cdb61c9f1 --- /dev/null +++ b/ezdop/AUTHORS @@ -0,0 +1 @@ +Johnathan Corgan <jcorgan@aeinet.com> diff --git a/ezdop/ChangeLog b/ezdop/ChangeLog new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ezdop/ChangeLog diff --git a/ezdop/Makefile.am b/ezdop/Makefile.am new file mode 100644 index 000000000..3f6865dbf --- /dev/null +++ b/ezdop/Makefile.am @@ -0,0 +1,26 @@ +# Copyright 2001,2002,2003,2004,2005,2006 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. + +include $(top_srcdir)/Makefile.common + +SUBDIRS = src +EXTRA_DIST = ezdop.pc.in + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = ezdop.pc diff --git a/ezdop/README b/ezdop/README new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ezdop/README diff --git a/ezdop/ezdop.pc.in b/ezdop/ezdop.pc.in new file mode 100644 index 000000000..fcfe1dd02 --- /dev/null +++ b/ezdop/ezdop.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: ezdop +Description: AE6HO EZ-Doppler +Requires: +Version: @VERSION@ +Libs: -L${libdir} -lftdi +Cflags: -I${includedir} @DEFINES@ diff --git a/ezdop/src/Makefile.am b/ezdop/src/Makefile.am new file mode 100644 index 000000000..caca90edb --- /dev/null +++ b/ezdop/src/Makefile.am @@ -0,0 +1,22 @@ +# Copyright 2001,2002,2003,2004,2005,2006 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. + +include $(top_srcdir)/Makefile.common + +SUBDIRS = firmware host diff --git a/ezdop/src/firmware/Makefile.am b/ezdop/src/firmware/Makefile.am new file mode 100644 index 000000000..6cb674811 --- /dev/null +++ b/ezdop/src/firmware/Makefile.am @@ -0,0 +1,60 @@ +# Copyright 2001,2002,2003,2004,2005,2006 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. + +include $(top_srcdir)/Makefile.common + +datadir = $(prefix)/share/ezdop + +# Use avr-gcc versions of compiler and binutils +CC=$(AVRGCC) +OBJCOPY=$(AVROBJCOPY) + +# What's defined for rest of project not good for avr-gcc (no -pthread for instance) +override CFLAGS= + +noinst_PROGRAMS = \ + dopctrl.elf \ + dopctrl.hex + +EXTRA_DIST = \ + dopctrl.hex + +dopctrl_elf_CFLAGS=-mmcu=atmega8 -funsigned-char -funsigned-bitfields \ + -fpack-struct -fshort-enums -Wall -Wstrict-prototypes \ + -Wa,-adhlns=$(<:.c=.lst) + +dopctrl_elf_SOURCES = \ + dopctrl.c + +#dopctrl_elfdir = $(includedir) + +include_HEADERS = \ + dopctrl.h + +%.hex: %.elf + $(OBJCOPY) -O ihex -R .eeprom $< $@ + +dopctrl.hex : dopctrl.elf + +install-data-local: + $(INSTALL_DATA) -D $(top_builddir)/ezdop/src/firmware/dopctrl.hex $(DESTDIR)$(datadir)/dopctrl.hex + +uninstall-local: + $(RM) $(DESTDIR)$(datadir)/dopctrl.hex +
\ No newline at end of file diff --git a/ezdop/src/firmware/dopctrl.c b/ezdop/src/firmware/dopctrl.c new file mode 100644 index 000000000..f499d6c35 --- /dev/null +++ b/ezdop/src/firmware/dopctrl.c @@ -0,0 +1,193 @@ +/* + * AE6HO EZ-Doppler firmware for onboard ATmega8 microcontroller + * Copyright 2006 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. + */ + +/* + * ARCHITECTURE + * + * Timer0 is 8000 Hz time base + * PORTD.2 through PORTD.5 are doppler antenna array element enables. + * PORTB.0 is a diagnostic LED, set low to light up + * ADC7 is audio input + * ADC6 is RSSI input - not yet used + * + */ + +#include <avr/io.h> +#include <avr/interrupt.h> +#include <avr/signal.h> +#include "dopctrl.h" + +#define LED 0 +#define turn_off_led() PORTB |= _BV(LED); +#define turn_on_led() PORTB &= ~_BV(LED); + +#define ANT1 _BV(2) +#define ANT2 _BV(3) +#define ANT3 _BV(4) +#define ANT4 _BV(5) +#define ANTMASK ANT1|ANT2|ANT3|ANT4 + +#define ADCAIN 7 +#define BAUDRATE 250000 + +/* Assume these are all set to zero in startup code */ + +uint8_t rotate; /* Flag to indicate antennas should rotate or not */ +uint8_t streaming; /* Flag to indicate continuous sampling */ +uint8_t antennas; /* Holds shadow copy of PORTD antennas */ +uint8_t speed; /* Holds samples per phase increment */ +uint8_t phase; /* Holds rotation phase (measured in samples */ + +uint8_t audio_hi; /* High byte of ADC sample of audio */ +uint8_t audio_lo; /* Low byte of ADC sample of audio */ + +uint8_t rx; /* Temporary holds received byte from USART */ +uint8_t command; /* Temporary to hold command when getting operand */ +uint8_t cmdbyte; /* Tracks bytes received in multi-byte commands */ + +int main(void) +{ + /* Diagnostic port setup */ + DDRB = _BV(LED); /* PB0 is output */ + turn_off_led(); + + /* Antenna control port setup */ + speed = 4; /* Todo: read from EEPROM */ + antennas = ANT1; /* Start with antenna #1 */ + PORTD = antennas; /* Set port value */ + DDRD = ANTMASK; /* Set antenna enables as PORTD outputs */ + + /* ADC port setup */ + ADMUX = _BV(REFS0)|ADCAIN; /* AVCC is reference, use ADC for audio input (ADC7) */ + ADCSRA = _BV(ADEN)|_BV(ADIE)|0x07; /* Enable converter, prescale by 128, enable ADC interrupt */ + + /* USART port setup*/ + UCSRA = 0; /* Normal asynchronous mode */ + UCSRB = _BV(TXEN)|_BV(RXEN)|_BV(RXCIE); /* Enable transmitter and receiver, and receiver interrupts */ + UCSRC = _BV(URSEL)|_BV(UCSZ1)|_BV(UCSZ0); /* 8N1 format */ + UBRRH = 0; /* Set baud rate prescaler to 3 */ + UBRRL = 3; /* To get 250000 bps */ + + /* Set up 8000 Hz time base */ + timer_enable_int(_BV(TOIE0)); /* Turn on Timer0 output overflow interrupt */ + TCCR0 = _BV(CS01); /* Clock Timer0 from CLK/8 */ + + sei(); /* Let 'er rip! */ + return 0; +} + +/* Timer0 overflow interrupt handler + * + * Creates 8000 Hz time base, or 125us budget + * + */ +SIGNAL(SIG_OVERFLOW0) +{ + /* Reload Timer0 samples to 8, results in 8000 Hz overflow interrupt */ + TCNT0 = 0x08; + + if (streaming) { + /* Kick-off an audio sample conversion, will interrupt 104us later */ + ADCSRA |= _BV(ADSC); + + /* Write the first byte of previous sample and enable UDRIE */ + UDR = audio_lo; + UCSRB |= _BV(UDRIE); + } + + if (!rotate) /* Skip rotating antenna if not started */ + return; + + /* Increment antenna phase and see if antenna need to be rotated */ + if (++phase == speed) { + phase = 0; + + /* Sequence antenna array elements */ + antennas >>= 1; + antennas &= ANTMASK; + + if (!antennas) + antennas = ANT4; + PORTD = antennas; + } +} + +/* ADC conversion complete interrupt handler + * + * Read value and store. Assume prior sample has been handled. + * + */ +SIGNAL(SIG_ADC) +{ + audio_lo = ADCL; + audio_hi = ADCH; +} + +/* USART data transmit holding register empty interrupt handler + * + * First byte is always sent from timer interrupt + * So second byte gets sent here with UDRIE disabled + * + */ +SIGNAL(SIG_UART_DATA) +{ + /* Write second byte of previous sample and disable UDRIE */ + UDR = audio_hi | (antennas << 2); + UCSRB &= ~_BV(UDRIE); +} + +/* USART receive complete interrupt handler + * + * Received bytes are commands, with one or two bytes of operands following + * + */ +SIGNAL(SIG_UART_RECV) +{ + rx = UDR; + + if (cmdbyte == 0) { + if (rx == EZDOP_CMD_ROTATE) /* Start rotation */ + rotate = 1; + else if (rx == EZDOP_CMD_STOP) /* Stop rotation */ + rotate = 0; + else if (rx == EZDOP_CMD_RATE) { /* Set rotation rate */ + command = rx; + cmdbyte = 1; + } + else if (rx == EZDOP_CMD_STREAM) /* Stream audio samples */ + streaming = 1; + else if (rx == EZDOP_CMD_STROFF) /* Stop streaming */ + streaming = 0; + else + turn_on_led(); /* Unknown command */ + } + else if (cmdbyte == 1) { + if (command == EZDOP_CMD_RATE) { /* Operand is number of samples per phase increment */ + speed = rx; + cmdbyte = 0; + } + else + turn_on_led(); /* Bogus command state */ + } + else + turn_on_led(); /* Bogus command state */ +} diff --git a/ezdop/src/firmware/dopctrl.h b/ezdop/src/firmware/dopctrl.h new file mode 100644 index 000000000..b689990dc --- /dev/null +++ b/ezdop/src/firmware/dopctrl.h @@ -0,0 +1,38 @@ +/* + * Copyright 2006 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. + */ + +// Opcodes for commands to AVR +#define EZDOP_CMD_ROTATE 0x00 /* No operand */ +#define EZDOP_CMD_STOP 0x01 /* No operand */ +#define EZDOP_CMD_RATE 0x02 /* One byte operand */ +#define EZDOP_CMD_STREAM 0x03 /* No operand */ +#define EZDOP_CMD_STROFF 0x04 /* No operand */ + +#define EZDOP_DEFAULT_RATE 4 + +// TODO: Update with actual id's once established for product +// These are for FTDI unprogrammed parts +#define EZDOP_VENDORID 0x0403 +#define EZDOP_PRODUCTID 0x6001 + +// These determine the FTDI bitbang settings +#define EZDOP_BAUDRATE 250000 // Fixed in EZ Doppler hardware design +#define EZDOP_BBDIR 0x16 // Bits 4=RESET 2=SCK 1=MOSI 0=MISO, MISO is input diff --git a/ezdop/src/host/Makefile.am b/ezdop/src/host/Makefile.am new file mode 100644 index 000000000..f7efaf7bf --- /dev/null +++ b/ezdop/src/host/Makefile.am @@ -0,0 +1,22 @@ +# Copyright 2001,2002,2003,2004,2005,2006 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. + +include $(top_srcdir)/Makefile.common + +SUBDIRS = avrdude ezdop tests diff --git a/ezdop/src/host/avrdude/Makefile.am b/ezdop/src/host/avrdude/Makefile.am new file mode 100644 index 000000000..962876da7 --- /dev/null +++ b/ezdop/src/host/avrdude/Makefile.am @@ -0,0 +1,29 @@ +# Copyright 2001,2002,2003,2004,2005,2006 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. + +include $(top_srcdir)/Makefile.common + +SUBDIRS = + +EXTRA_DIST = \ + README \ + ftbb.h \ + ftbb.c \ + ftbb_ftdi.m4 \ + patch-avrdude-5.1-ftbb.diff diff --git a/ezdop/src/host/avrdude/README b/ezdop/src/host/avrdude/README new file mode 100644 index 000000000..2eb37af5a --- /dev/null +++ b/ezdop/src/host/avrdude/README @@ -0,0 +1,7 @@ +These files add bitbang over FTDI USB controller as a programming option +to the avrdude Atmel AVR programmer. This is used to program the firmware +of the EZDOP microcontroller over the USB port. + +AVRDUDE is a free program available at: + +http://savannah.nongnu.org/projects/avrdude/ diff --git a/ezdop/src/host/avrdude/ftbb.c b/ezdop/src/host/avrdude/ftbb.c new file mode 100644 index 000000000..53b5794ca --- /dev/null +++ b/ezdop/src/host/avrdude/ftbb.c @@ -0,0 +1,412 @@ +/* + * avrdude - A Downloader/Uploader for AVR device programmers + * Copyright (C) 2003-2004 Theodore A. Roth <troth@openavr.org> + * Copyright (C) 2005 Johnathan Corgan <jcorgan@aeinet.com> + * + * This program 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 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * avrdude interface for serial programming via FTDI bit bang mode operation. + */ + +#include "ac_cfg.h" +#include "avr.h" +#include "pgm.h" +#include "ftbb.h" +#include <string.h> + +#if FTDI_SUPPORT +#define FTBB_DEBUG 0 + +#if HAVE_LIBFTD2XX +#include <ftd2xx.h> +#elif HAVE_FTDI_H +#include <ftdi.h> +#endif + +#define RESET (1<<(pgm->pinno[PIN_AVR_RESET]-1)) +#define SCK (1<<(pgm->pinno[PIN_AVR_SCK]-1)) +#define MOSI (1<<(pgm->pinno[PIN_AVR_MOSI]-1)) +#define MISO (1<<(pgm->pinno[PIN_AVR_MISO]-1)) +#define FTDDR (MOSI|SCK|RESET) + +#if HAVE_LIBFTD2XX +static FT_STATUS status; +static FT_HANDLE handle; +#elif HAVE_FTDI_H +static struct ftdi_context device; +static int status; +#endif + +static unsigned char txbits; + +/* Send 8 bits over SPI bus with per-bit read back + * + */ +static unsigned char ftbb_txrx(PROGRAMMER *pgm, unsigned char val) +{ + int i; + unsigned char bits; + unsigned char res; + +#if HAVE_LIBFTD2XX + DWORD written; +#endif /* HAVE_LIBFTD2XX */ + + res = 0; + bits = 0; + for (i = 0; i < 8; i++) { // For each bit + // Set up data on low phase of SCK + txbits &= ~SCK; + + // Set MOSI to high bit of transmit data + if (val & 0x80) + txbits |= MOSI; + else + txbits &= ~MOSI; + +#if HAVE_LIBFTD2XX + FT_Write(handle, &txbits, 1, &written); + // Clock just fell, read previous input bit and shift in + FT_GetBitMode(handle, &bits); +#elif HAVE_FTDI_H + ftdi_write_data(&device, &txbits, 1); + // Clock just fell, read previous input bit and shift in + ftdi_read_pins(&device, &bits); +#endif /* HAVE_LIBFTD2XX */ + res = (res << 1) | (bits & MISO); + + // Now raise SCK to latch data in AVR + txbits |= SCK; + +#if HAVE_LIBFTD2XX + FT_Write(handle, &txbits, 1, &written); +#elif HAVE_FTDI_H + ftdi_write_data(&device, &txbits, 1); +#endif /* HAVE_LIBFTD2XX */ + // Move on to next bit in transmit data + val = val << 1; + } + + return res; +} + +/* Generic programmer command function for pgm->cmd + * + * Sends four bytes of command in sequence and collects responses + * + */ +static int ftbb_cmd(PROGRAMMER *pgm, unsigned char cmd[4], unsigned char res[4]) +{ + int i; + +#if FTBB_DEBUG + printf("CMD: %02X%02X%02X%02X ", cmd[0], cmd[1], cmd[2], cmd[3]); +#endif + + for (i = 0; i < 4; i++) { + res[i] = ftbb_txrx(pgm, cmd[i]); + } + +#if FTBB_DEBUG + printf("RES: %02X%02X%02X%02X\n", res[0], res[1], res[2], res[3]); +#endif + + return 0; +} + +/* Programmer initialization command for pgm->initialize + * + * Pulse RESET with SCK low, then send SPI start programming command + * + */ +static int ftbb_initialize(PROGRAMMER *pgm, AVRPART *p) +{ +#if HAVE_LIBFTD2XX + DWORD written; +#endif /* HAVE_LIBFTD2XX */ + + // Atmel documentation says to raise RESET for 2 cpu clocks while sclk is low + // then lower RESET and wait 20 ms. + txbits |= RESET; + +#if HAVE_LIBFTD2XX + FT_Write(handle, &txbits, 1, &written); +#elif HAVE_FTDI_H + ftdi_write_data(&device, &txbits, 1); +#endif /* HAVE_LIBFTD2XX */ + + usleep(1000); // 2 AVR cpu clocks at any realistic clock rate + txbits &= ~RESET; + +#if HAVE_LIBFTD2XX + FT_Write(handle, &txbits, 1, &written); +#elif HAVE_FTDI_H + ftdi_write_data(&device, &txbits, 1); +#endif /* HAVE_LIBFTD2XX */ + + usleep(20000); + + pgm->program_enable(pgm, p); + return 0; +} + +/* Programmer status display for pgm->display + * + * Not-implemented + * + */ +static void ftbb_display(PROGRAMMER *pgm, char *p) +{ +#if FTBB_DEBUG + printf("ftbb: display called with: %s\n", p); +#endif + + printf("FTBB: RESET mapped to pinno %d\n", pgm->pinno[PIN_AVR_RESET]); + printf("FTBB: SCK mapped to pinno %d\n", pgm->pinno[PIN_AVR_SCK]); + printf("FTBB: MOSI mapped to pinno %d\n", pgm->pinno[PIN_AVR_MOSI]); + printf("FTBB: MISO mapped to pinno %d\n", pgm->pinno[PIN_AVR_MISO]); +} + +/* Programmer enable command for pgm->enable + * + * Lowers SCK and RESET in preparation for serial programming + * + */ +static void ftbb_enable(PROGRAMMER *pgm) +{ +#if HAVE_LIBFTD2XX + DWORD written; +#endif /* HAVE_LIBFTD2XX */ + + // Lower SCK & RESET + txbits &= ~SCK; + txbits &= ~RESET; + +#if HAVE_LIBFTD2XX + FT_Write(handle, &txbits, 1, &written); +#elif HAVE_FTDI_H + ftdi_write_data(&device, &txbits, 1); +#endif /* HAVE_LIBFTD2XX */ +} + +/* Programmer disable command for pgm->disable + * + * Raises RESET to return to normal chip operation + * + */ +static void ftbb_disable(PROGRAMMER *pgm) +{ +#if HAVE_LIBFTD2XX + DWORD written; +#endif /* HAVE_LIBFTD2XX */ + // Raise RESET to return to normal mode + txbits |= RESET; + +#if HAVE_LIBFTD2XX + FT_Write(handle, &txbits, 1, &written); +#elif HAVE_FTDI_H + ftdi_write_data(&device, &txbits, 1); +#endif /* HAVE_LIBFTD2XX */ +} + +/* Programmer programming mode enable function for pgm->program_enable + * + * Starts SPI programming mode + * + */ +static int ftbb_program_enable(PROGRAMMER *pgm, AVRPART *p) +{ + unsigned char cmd[4]; + unsigned char res[4]; + + if (p->op[AVR_OP_PGM_ENABLE] == NULL) { + fprintf(stderr, "program enable instruction not defined for part \"%s\"\n", p->desc); + return -1; + } + + memset(cmd, 0, sizeof(cmd)); + avr_set_bits(p->op[AVR_OP_PGM_ENABLE], cmd); + pgm->cmd(pgm, cmd, res); + + return 0; +} + +/* Progammer erase function for pgm->erase + * + * Sends chip erase command and sleeps the chip erase delay + * + */ +static int ftbb_chip_erase(PROGRAMMER *pgm, AVRPART *p) +{ + unsigned char cmd[4]; + unsigned char res[4]; + + if (p->op[AVR_OP_CHIP_ERASE] == NULL) { + fprintf(stderr, "chip erase instruction not defined for part \"%s\"\n", p->desc); + return -1; + } + + memset(cmd, 0, sizeof(cmd)); + avr_set_bits(p->op[AVR_OP_CHIP_ERASE], cmd); + pgm->cmd(pgm, cmd, res); + usleep(p->chip_erase_delay); + + return 0; +} + + +/* Parse routine for device name + * + * FFFF:FFFF:0 + * + */ +int ftbb_parse_name(char *name, int *vid, int *pid, int *ifc) +{ + printf("name=%s\n", name); + return 0; +} + +/* Programmer open command for pgm->open + * + * Opens FTD2XX device specified by 'name', performs a chip reset, then + * sets the baudrate and bit bang mode + * + */ +static int ftbb_open(PROGRAMMER *pgm, char *name) +{ + int vid, pid, ifc; + ftbb_parse_name(name, &vid, &pid, &ifc); + +#if HAVE_LIBFTD2XX + int devnum = 0; + if (strcmp(name, "ft0")) { + fprintf(stderr, "ERROR: FTD2XX device selection not yet implemented!\n"); + return -1; + } + + // Call FTD2XX library to open device + if ((status = FT_Open(devnum, &handle)) != FT_OK) { + fprintf(stderr, "Failed to open FTD2XX device #%d.\n", devnum); + return -1; + } + + // Reset chipset + if ((status = FT_ResetDevice(handle)) != FT_OK) { + fprintf(stderr, "Failed to reset chipset for FTD2XX device #%d.\n", devnum); + return -1; + } + + // Set baud rate for bit bang interface + if ((status = FT_SetBaudRate(handle, pgm->baudrate)) != FT_OK) { + fprintf(stderr, "Failed to set baud rate for FTD2XX device #%d.\n", devnum); + return -1; + } + + // Set bit bang direction and mode + if ((status = FT_SetBitMode(handle, FTDDR, 1)) != FT_OK) { + fprintf(stderr, "Failed to set bit bang mode for FTD2XX device #%d.\n", devnum); + return -1; + } +#elif HAVE_FTDI_H + // Open device via FTDI library *** FIXME *** hardcoded VID and PID + if (ftdi_usb_open(&device, EZDOP_VENDORID, EZDOP_PRODUCTID)) { + fprintf(stderr, "ftdi_usb_open: %s", device.error_str); + return -1; + } + + // Reset FTDI chipset + if (ftdi_usb_reset(&device)) { + fprintf(stderr, "ftdi_usb_reset: %s", device.error_str); + return -1; + } + + // Set FTDI chipset baudrate for bitbang + if (ftdi_set_baudrate(&device, pgm->baudrate)) { + fprintf(stderr, "ftdi_set_baudrate: %s", device.error_str); + return -1; + } + + // Enable bitbang + if (ftdi_enable_bitbang(&device, FTDDR)) { + fprintf(stderr, "ftdi_enable_bitbang: %s", device.error_str); + return -1; + } + + // Minimum chunk size for reads to reduce latency + if (ftdi_read_data_set_chunksize(&device, 256)) { + fprintf(stderr, "ftdi_read_data_set_chunksize: %s", device.error_str); + return -1; + } +#endif /* HAVE_LIBFTD2XX */ + + return 0; +} + +/* Programmer close function for pgm->close + * + * Releases FTD2XX device + * + */ +static void ftbb_close(PROGRAMMER *pgm) +{ +#if HAVE_LIBFTD2XX + FT_Close(handle); +#elif HAVE_FTDI_H + ftdi_deinit(&device); +#endif +} +#endif /* FTDI_SUPPORT */ + +/* Programmer initialization command for startup. + * + * Sets appropriate function pointers in structure + * Tests FTD2XX interface by enumerating number of devices + * + */ +void ftbb_initpgm (PROGRAMMER *pgm) +{ +#if FTDI_SUPPORT + +#if HAVE_LIBFTD2XX + DWORD num_devices; +#endif + + strcpy(pgm->type, "ftbb"); + pgm->initialize = ftbb_initialize; + pgm->display = ftbb_display; + pgm->enable = ftbb_enable; + pgm->disable = ftbb_disable; + pgm->program_enable = ftbb_program_enable; + pgm->chip_erase = ftbb_chip_erase; + pgm->cmd = ftbb_cmd; + pgm->open = ftbb_open; + pgm->close = ftbb_close; + +#if HAVE_LIBFTD2XX + if ((status = FT_ListDevices(&num_devices, NULL, FT_LIST_NUMBER_ONLY)) != FT_OK) + fprintf(stderr, "Failed to initialize FTD2XX interface. (%li)\n", status); + else + printf("%lu FTD2XX device(s) found.\n", num_devices); +#elif HAVE_FTDI_H + if ((status = ftdi_init(&device)) < 0) + fprintf(stderr, "Failed to initialize FTDI interface. (%i)\n", status); +#endif + + txbits = RESET; +#endif /* FTDI_SUPPORT */ +} + diff --git a/ezdop/src/host/avrdude/ftbb.h b/ezdop/src/host/avrdude/ftbb.h new file mode 100644 index 000000000..d51e1a954 --- /dev/null +++ b/ezdop/src/host/avrdude/ftbb.h @@ -0,0 +1,33 @@ +/* + * avrdude - A Downloader/Uploader for AVR device programmers + * Copyright (C) 2003-2004 Theodore A. Roth <troth@openavr.org> + * Copyright (C) 2005 Johnathan Corgan <jcorgan@aeinet.com> + * + * This program 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 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __ftbb_h__ +#define __ftbb_h__ + +#include "avrpart.h" + +void ftbb_initpgm (PROGRAMMER *pgm); + +// TODO: Update with actual id's once established for product +// These are for FTDI unprogrammed parts +#define EZDOP_VENDORID 0x0403 +#define EZDOP_PRODUCTID 0x6001 + +#endif /* __ftbb_h__ */ diff --git a/ezdop/src/host/avrdude/ftbb_ftdi.m4 b/ezdop/src/host/avrdude/ftbb_ftdi.m4 new file mode 100644 index 000000000..ce020868b --- /dev/null +++ b/ezdop/src/host/avrdude/ftbb_ftdi.m4 @@ -0,0 +1,56 @@ +# +# avrdude - A Downloader/Uploader for AVR device programmers +# Copyright (C) 2003-2004 Theodore A. Roth <troth@openavr.org> +# Copyright (C) 2005 Johnathan Corgan <jcorgan@aeinet.com> +# +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +AC_DEFUN([FTBB_FTDI], +[ + AC_ARG_ENABLE( + [ftdi-support], + AC_HELP_STRING( + [--enable-ftdi-support], + [support serial programming via FTDI bit-bang mode (default is no)]), + [case "${enableval}" in + yes) ftdi_support=yes ;; + no) ftdi_support=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for ftdi-support option) ;; + esac], + [ftdi_support=no]) + AM_CONDITIONAL(FTDI_SUPPORT, [test "$ftdi_support" = "yes"]) + + if test "$ftdi_support" = "yes"; then + AC_REQUIRE([AC_CANONICAL_HOST]) + AC_LANG_PUSH(C) + + AC_CHECK_HEADERS([ftdi.h], + [], + [ AC_MSG_ERROR([FTDI support requires libftdi. ftdi.h not found, stop.]) ] + ) + + save_LIBS="$LIBS" + AC_SEARCH_LIBS(ftdi_init, [ftdi], + [ FTDILIB="$LIBS" ], + [ AC_MSG_ERROR([FTDI support requires libftdi. ftdi_init not found, stop.]) ] + ) + LIBS="$save_LIBS" + + AC_LANG_POP + AC_SUBST(FTDILIB) + + AC_DEFINE(FTDI_SUPPORT,[1],[Set to 1 to compile in support for FTDI bit-bang mode programming.]) + fi +]) diff --git a/ezdop/src/host/avrdude/patch-avrdude-5.1-ftbb.diff b/ezdop/src/host/avrdude/patch-avrdude-5.1-ftbb.diff new file mode 100644 index 000000000..05836df20 --- /dev/null +++ b/ezdop/src/host/avrdude/patch-avrdude-5.1-ftbb.diff @@ -0,0 +1,133 @@ +Index: Makefile.am +=================================================================== +RCS file: /sources/avrdude/avrdude/Makefile.am,v +retrieving revision 1.29 +diff -u -r1.29 Makefile.am +--- Makefile.am 12 Jan 2006 23:13:50 -0000 1.29 ++++ Makefile.am 7 Jul 2006 03:57:10 -0000 +@@ -71,6 +71,8 @@ + fileio.c \ + fileio.h \ + freebsd_ppi.h \ ++ ftbb.c \ ++ ftbb.h \ + jtagmkI.c \ + jtagmkI.h \ + jtagmkI_private.h \ +@@ -109,6 +111,8 @@ + usbdevs.h \ + usb_libusb.c + ++avrdude_LDADD = $(FTDILIB) ++ + man_MANS = avrdude.1 + + sysconf_DATA = avrdude.conf +Index: avrdude.conf.in +=================================================================== +RCS file: /sources/avrdude/avrdude/avrdude.conf.in,v +retrieving revision 1.79 +diff -u -r1.79 avrdude.conf.in +--- avrdude.conf.in 23 May 2006 22:27:43 -0000 1.79 ++++ avrdude.conf.in 7 Jul 2006 03:57:10 -0000 +@@ -223,6 +223,17 @@ + # + + programmer ++ id = "ezdop"; ++ desc = "AE6HO EZ-Doppler"; ++ type = ftbb; ++ baudrate = 230400; ++ reset = 5; ++ sck = 3; ++ mosi = 2; ++ miso = 1; ++; ++ ++programmer + id = "avrisp"; + desc = "Atmel AVR ISP"; + type = stk500; +Index: bootstrap +=================================================================== +RCS file: /sources/avrdude/avrdude/bootstrap,v +retrieving revision 1.10 +diff -u -r1.10 bootstrap +--- bootstrap 14 May 2005 08:06:18 -0000 1.10 ++++ bootstrap 7 Jul 2006 03:57:10 -0000 +@@ -40,7 +40,7 @@ + + rm -rf autom4te.cache + +-${ACLOCAL} ++${ACLOCAL} -I . + ${AUTOHEADER} + ${AUTOCONF} + ${AUTOMAKE} -a -c +Index: config_gram.y +=================================================================== +RCS file: /sources/avrdude/avrdude/config_gram.y,v +retrieving revision 1.45 +diff -u -r1.45 config_gram.y +--- config_gram.y 23 May 2006 22:27:43 -0000 1.45 ++++ config_gram.y 7 Jul 2006 03:57:10 -0000 +@@ -40,6 +40,7 @@ + #include "avr.h" + #include "jtagmkI.h" + #include "jtagmkII.h" ++#include "ftbb.h" + + #if defined(WIN32NATIVE) + #define strtok_r( _s, _sep, _lasts ) \ +@@ -123,6 +124,7 @@ + %token K_STK500V2 + %token K_AVR910 + %token K_BUTTERFLY ++%token K_FTBB + %token K_TYPE + %token K_VCC + %token K_VFYLED +@@ -374,6 +376,12 @@ + } + } | + ++ K_TYPE TKN_EQUAL K_FTBB { ++ { ++ ftbb_initpgm(current_prog); ++ } ++ } | ++ + K_DESC TKN_EQUAL TKN_STRING { + strncpy(current_prog->desc, $3->value.string, PGM_DESCLEN); + current_prog->desc[PGM_DESCLEN-1] = 0; +Index: configure.ac +=================================================================== +RCS file: /sources/avrdude/avrdude/configure.ac,v +retrieving revision 1.33 +diff -u -r1.33 configure.ac +--- configure.ac 23 Jan 2006 21:04:13 -0000 1.33 ++++ configure.ac 7 Jul 2006 03:57:10 -0000 +@@ -115,6 +115,8 @@ + DIST_SUBDIRS_AC='windows' + fi + ++FTBB_FTDI ++ + AC_SUBST(DOC_INST_DIR, $DOC_INST_DIR) + AC_SUBST(SUBDIRS_AC, $SUBDIRS_AC) + AC_SUBST(DIST_SUBDIRS_AC, $DIST_SUBDIRS_AC) +Index: lexer.l +=================================================================== +RCS file: /sources/avrdude/avrdude/lexer.l,v +retrieving revision 1.38 +diff -u -r1.38 lexer.l +--- lexer.l 23 May 2006 22:27:43 -0000 1.38 ++++ lexer.l 7 Jul 2006 03:57:11 -0000 +@@ -138,6 +138,7 @@ + enablepageprogramming { yylval=NULL; return K_ENABLEPAGEPROGRAMMING; } + errled { yylval=NULL; return K_ERRLED; } + flash { yylval=NULL; return K_FLASH; } ++ftbb { yylval=NULL; return K_FTBB; } + has_jtag { yylval=NULL; return K_HAS_JTAG; } + id { yylval=NULL; return K_ID; } + idr { yylval=NULL; return K_IDR; } diff --git a/ezdop/src/host/ezdop/Makefile.am b/ezdop/src/host/ezdop/Makefile.am new file mode 100644 index 000000000..5f47e43b1 --- /dev/null +++ b/ezdop/src/host/ezdop/Makefile.am @@ -0,0 +1,33 @@ +# Copyright 2001,2002,2003,2004,2005,2006 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. + +include $(top_srcdir)/Makefile.common + +INCLUDES=$(EZDOP_INCLUDES) + +lib_LTLIBRARIES = \ + libezdop.la +libezdop_la_LIBADD = \ + $(FTDI_LIBS) + +libezdop_la_SOURCES = \ + ezdop.cc + +include_HEADERS = \ + ezdop.h
\ No newline at end of file diff --git a/ezdop/src/host/ezdop/ezdop.cc b/ezdop/src/host/ezdop/ezdop.cc new file mode 100644 index 000000000..43a70dacb --- /dev/null +++ b/ezdop/src/host/ezdop/ezdop.cc @@ -0,0 +1,297 @@ +/* + * Copyright 2006 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. + */ + +// Application specific includes +#include "ezdop.h" + +// System includes (FIXME: autoconf these) +#include <cassert> +#include <cstdio> +#include <unistd.h> + +ezdop::ezdop() +{ + d_device = new struct ftdi_context; + if (ftdi_init(d_device)) + fprintf(stderr, "ftdi_init: %s", d_device->error_str); + d_online = false; + d_rotating = false; + d_rate = EZDOP_DEFAULT_RATE; + d_state = ST_HI; + d_ant = 8; // FIXME: get from controller.h + d_seq = 0; + d_val = 0; + d_in_phase = 0; + d_quadrature = 0; + d_rotating = false; +} + +ezdop::~ezdop() +{ + assert(d_device); + delete d_device; +} + +bool ezdop::init() +{ + assert(d_device); + + d_online = false; + + // Attaches to first found device matching ID strings + if (ftdi_usb_open(d_device, EZDOP_VENDORID, EZDOP_PRODUCTID)) { + fprintf(stderr, "ftdi_usb_open: %s", d_device->error_str); + return false; + } + + d_online = true; + reset(); + + return d_online; +} + +bool ezdop::finish() +{ + assert(d_device); + + if (!d_online) + return true; + + if (d_rotating) + stop_rotating(); + + if (ftdi_usb_close(d_device)) { + fprintf(stderr, "ftdi_usb_close: %s", d_device->error_str); + return false; + } + + d_online = false; + return true; +} + +bool ezdop::reset() +{ + assert(d_device); + assert(d_online); + + // Reset FTDI chipset + if (ftdi_usb_reset(d_device)) { + fprintf(stderr, "ftdi_usb_reset: %s", d_device->error_str); + return false; + } + + // Set FTDI chipset baudrate for bitbang + if (ftdi_set_baudrate(d_device, EZDOP_BAUDRATE)) { + fprintf(stderr, "ftdi_set_baudrate: %s", d_device->error_str); + return false; + } + + // Toggle DTR (-->AVR RESET) + // Enable bitbang + if (ftdi_enable_bitbang(d_device, EZDOP_BBDIR)) { + fprintf(stderr, "ftdi_enable_bitbang: %s", d_device->error_str); + return false; + } + + // Lower DTR by writing 0 to bitbang output + if (!send_byte(0x00)) // This actually lowers all outputs, not just DTR + return false; + + // 10 ms sleep with RESET low + usleep(10000); + + // Now raise DTR by writing 1 to bitbang output + if (!send_byte(0xFF)) // This actually raises all outputs, not just DTR + return false; + + if (ftdi_disable_bitbang(d_device)) { + fprintf(stderr, "ftdi_disable_bitbang: %s", d_device->error_str); + return false; + } + + // Minimum chunk size for reads to reduce latency + if (ftdi_read_data_set_chunksize(d_device, 256)) { + fprintf(stderr, "ftdi_read_data_set_chunksize: %s", d_device->error_str); + return false; + } + + // 100 ms after RESET cleared to let things warm up + usleep(100000); + + d_rate = EZDOP_DEFAULT_RATE; + return true; +} + +bool ezdop::set_rate(int rate) +{ + assert(d_device); + assert(d_online); + + // Rate command is one byte, followed by rate as operand + int divisor = 2000/rate; + if (send_byte(EZDOP_CMD_RATE) && send_byte((unsigned char)divisor)) { + d_rate = divisor; + return true; + } + + return false; +} + +bool ezdop::rotate() +{ + assert(d_online); + assert(d_device); + + d_rotating = send_byte(EZDOP_CMD_ROTATE); + return d_rotating; +} + +bool ezdop::stop_rotating() +{ + assert(d_online); + assert(d_device); + + // TODO: set to antenna #1, perhaps do this in firmware instead + d_rotating = send_byte(EZDOP_CMD_STOP); + return d_rotating; +} + +bool ezdop::stream() +{ + assert(d_online); + assert(d_device); + + return (send_byte(EZDOP_CMD_STREAM)); +} + +bool ezdop::stop_streaming() +{ + assert(d_online); + assert(d_device); + + return (send_byte(EZDOP_CMD_STROFF)); +} + +bool ezdop::send_byte(unsigned char data) +{ + assert(d_online); + assert(d_device); + + if (ftdi_write_data(d_device, &data, 1) != 1) { + fprintf(stderr, "ftdi_write_data: %s", d_device->error_str); + return false; + } + + return true; +} + +int ezdop::read_raw(unsigned char *buffer, unsigned int length) +{ + assert(d_online); + assert(d_device); + assert(buffer); + + // Read samples from USB port, 2 bytes per sample + int rd = ftdi_read_data(d_device, buffer, length); + if (rd < 0) { + fprintf(stderr, "ftdi_read_data: %s", d_device->error_str); + return -1; + } + + return rd; +} + +int ezdop::read_iq(complex<float> *buffer, unsigned int samples) +{ + assert(d_online); + assert(d_device); + assert(buffer); + + // 4 phases, d_rate samples per phase, 2 bytes per sample + int bufsize = 8*d_rate*samples; + unsigned char *raw = new unsigned char[bufsize]; + + // Read until required bytes are read. Will block until bytes arrive. + int rd = 0; + while (rd < bufsize) + rd += read_raw(&raw[rd], bufsize-rd); + + // Iterate through read bytes and invoke state machine + int i = 0, j = 0; // i index inputs, j indexes outputs + unsigned char ant; + + while (i < bufsize) { + unsigned char ch = raw[i++]; + if (d_state == ST_LO) { + d_val = ch; // Save lo byte + d_state = ST_HI; // Switch states + continue; // Done with this state + } + + if (d_state == ST_HI) { + unsigned char ant = ch >> 4; // antenna is high nibble + if (ant != d_ant) { // Didn't get expected antenna + // Abort current sequence + d_ant = 8; + d_seq = 0; + d_val = 0; + d_in_phase = 0; d_quadrature = 0; + d_val = ch; // Act as if this were a lo byte instead + continue; // Stay in ST_HI + } + } + + // Got correct antenna + d_val |= (ch & 0x03) << 8; // Mask off and save audio high value + + // This down-converts rotation frequency to exactly 0 Hz + // while integrating audio response over duration of one antenna phase + if (d_ant == 8) // +I + d_in_phase += d_val; + else if (d_ant == 4) // +Q + d_quadrature += d_val; + else if (d_ant == 2) // -I + d_in_phase -= d_val; + else if (d_ant == 1) // -Q + d_quadrature -= d_val; + d_val = 0; + + // Update expected antenna and sequence + if (++d_seq == d_rate) { + d_ant = d_ant >> 1; + d_seq = 0; + if (d_ant == 0) { // fell off the end + d_ant = 8; // FIXME: grab from controller.h + + // We've accumulated I and Q over a whole antenna rotation + // Output complex<float> in range [-1.0, 1.0] + buffer[j++] = complex<float>(d_in_phase/(1024.0*d_rate), + d_quadrature/(1024.0*d_rate)); + d_in_phase = 0; d_quadrature = 0; + } + } + + d_state = ST_LO; // Switch states + }; + + delete raw; + return j; +} diff --git a/ezdop/src/host/ezdop/ezdop.h b/ezdop/src/host/ezdop/ezdop.h new file mode 100644 index 000000000..b4ce1a454 --- /dev/null +++ b/ezdop/src/host/ezdop/ezdop.h @@ -0,0 +1,81 @@ +/* + * Copyright 2006 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. + */ + +#ifndef __EZDOP_H__ +#define __EZDOP_H__ + +// Application level includes +#include <dopctrl.h> + +// System level includes +#include <ftdi.h> +#include <complex> + +using std::complex; + +class ezdop +{ +public: + ezdop(); + ~ezdop(); + + // Housekeeping + bool init(); // Attach to first device found, TODO: serial no + bool finish(); // Release device + bool reset(); // Reset state to post-init() + + // Parameters + bool set_rate(int rate); // Set rotation rate + int rate() const // Get rotation rate + { return 2000/d_rate; } + + // Commands + bool rotate(); // Start antenna rotation + bool stop_rotating(); // Stop antenna rotation + bool stream(); // Start audio streaming + bool stop_streaming(); // Stop audio streaming + + // Raw read of sample stream from device, length in bytes (2 per sample) + int read_raw(unsigned char *buffer, unsigned int length); + + // Read synced, downconverted I and Q samples, one per rotation + int read_iq(complex<float> *buffer, unsigned int samples); + + // Status + bool is_online() const { return d_online; } + +private: + struct ftdi_context *d_device; // libftdi device instance data + bool d_online; // Successfully found and connected to device + bool d_rotating; // Doppler is rotating + int d_rate; // Current rotation rate (samples per antenna) + enum { ST_HI, ST_LO } d_state; // Current byte sync state + char d_ant; // Current antenna being sequenced + int d_seq; // Current phase sample number + int d_val; // Current reassembled audio sample value + int d_in_phase; // Downconverted I accumulator + int d_quadrature; // Downconverted Q accumulator + + bool send_byte(unsigned char data); // Send a byte to AVR + +}; + +#endif 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 <jcorgan@aeinet.com> diff --git a/ezdop/src/host/hunter/ChangeLog b/ezdop/src/host/hunter/ChangeLog new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ezdop/src/host/hunter/ChangeLog 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 --- /dev/null +++ b/ezdop/src/host/hunter/NEWS 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 <wx/xrc/xmlres.h> +#include <wx/button.h> +#include <wx/gauge.h> +#include <wx/stattext.h> +#include <wx/log.h> + +// 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 <wx/event.h> +#include <wx/dialog.h> +#include <wx/gauge.h> // 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 <dopctrl.h> + +// wxWidgets includes +#include <wx/log.h> +#include <wx/frame.h> + +// System level includes +#include <cmath> + +#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 <ftdi.h> +#elif HAVE_LIBFTD2XX + #if __WIN32__ + #include <windows.h> + #endif + #include <FTD2XX.H> +#endif + +#include <wx/event.h> + +#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 <wx/log.h> + +#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 <wx/event.h> + +// System level includes +#include <math.h> // For some reason, <cmath> 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 <wx/log.h> +#include <wx/dc.h> +#include <wx/dcclient.h> + +using namespace std; + +ErrorHistogram::ErrorHistogram() +{ + Reset(); +} + +void ErrorHistogram::Reset() +{ + m_bins = vector<float>(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<Sample> &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<float> &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 <wx/panel.h> + +// System level includes +#include <vector> + +class ErrorHistogram +{ +public: + ErrorHistogram(); + + void Reset(); + void Calc(const std::vector<Sample> &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<float> &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<float> 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 Binary files differnew file mode 100644 index 000000000..6d67dd46d --- /dev/null +++ b/ezdop/src/host/hunter/src/hunter.bmp 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 <wx/xrc/xmlres.h> +#include <wx/wx.h> // Too many to do individually +#include <wx/file.h> // 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 <gravity> 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 <wx/frame.h> +#include <wx/spinbutt.h> // Unknown why these two aren't in wx.h +#include <wx/splitter.h> + +// 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 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<resource xmlns="http://www.wxwidgets.org/wxxrc" version="2.3.0.1"> + +<object class="wxMenuBar" name="main_menu"> + <object class="wxMenu" name="file_menu"> + <label>_File</label> + <object class="wxMenuItem" name="file_new_menuitem"> + <label>_New Search\tCtrl-N</label> + <help>Create a new transmitter search.</help> + </object> + <object class="wxMenuItem" name="file_open_menuitem"> + <label>_Open Search\tCtrl-O</label> + <help>Open an existing search</help> + </object> + <object class="wxMenuItem" name="file_close_menuitem"> + <label>_Close Search\tCtrl-W</label> + <help>Close current search.</help> + </object> + <object class="separator"/> + <object class="wxMenuItem" name="file_save_menuitem"> + <label>_Save Search\tCtrl-S</label> + <help>Save current search to a file.</help> + </object> + <object class="separator"/> + <object class="wxMenuItem" name="file_exit_menuitem"> + <label>E_xit\tAlt-X</label> + <help>Exit this application</help> + </object> + </object> + + <object class="wxMenu" name="doppler_menu"> + <label>_Doppler</label> + <object class="wxMenuItem" name="doppler_toggle_menuitem"> + <label>_Start</label> + <help>Start Doppler Rotation</help> + </object> + <object class="wxMenuItem" name="doppler_autostart_menuitem"> + <label>_Autostart</label> + <checkable>1</checkable> + <help>Automatically start Doppler when application loads</help> + </object> + <object class="wxMenuItem" name="doppler_reset_menuitem"> + <label>_Reset</label> + <help>Reset Doppler</help> + </object> + </object> + + <object class="wxMenu" name="calibration_menu"> + <label>_Calibration</label> + <object class="wxMenuItem" name="calibration_loadfromfile_menuitem"> + <label>_Load Calibration File</label> + <help>Load system calibration values from filesystem</help> + </object> + <object class="wxMenuItem" name="calibration_savetofile_menuitem"> + <label>_Save Calibration File</label> + <help>Save system calibration values to filesystem</help> + </object> + <object class="separator"/> + <object class="wxMenuItem" name="calibration_loadtransmitter_menuitem"> + <label>L_oad Transmitter</label> + <help>Load transmitter coordinates from file</help> + </object> + <object class="wxMenuItem" name="calibration_savetransmitter_menuitem"> + <label>S_ave Transmitter</label> + <help>Save transmitter coordinates to file</help> + </object> + </object> + + <object class="wxMenu" name="help_menu"> + <label>_Help</label> + <object class="wxMenuItem" name="about_menuitem"> + <label>_About...</label> + <help>About this application</help> + </object> + </object> +</object> + +<object class="wxFrame" name="main_frame"> <!-- Main window, bottom left and right panes --> + <title>AE6HO Radio Location System</title> + <object class="wxSplitterWindow" name="horiz_splitter"> <!-- Divides main window into top and bottom --> + <style>wxSP_3D|wxSP_LIVE_UPDATE</style> + <orientation>horizontal</orientation> + <minsize>170</minsize> + + <object class="wxSplitterWindow" name="vert_splitter"> <!-- Divides top half into left and right --> + <style>wxSP_3D|wxSP_LIVE_UPDATE</style> + <orientation>vertical</orientation> + <minsize>240</minsize> + <sashpos>240</sashpos> + <object_ref ref="status_panel"/> <!-- Defined below --> + <object class="unknown" name="tactical_panel"/> + </object> + + <object_ref ref="control_panel"/> <!-- Defined below --> + + </object> + +</object> + +<object class="wxPanel" name="status_panel"> <!-- Read only display variables --> + <object class="wxBoxSizer"> <!-- Each item is a major status disply, doppler, GPS, etc. --> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <flag>wxGROW|wxALL</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> <!-- Each item is a Doppler status control --> + <label>Doppler</label> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <flag>wxGROW|wxLEFT|wxRIGHT|wxBOTTOM</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> <!-- Single item, doppler strength --> + <orient>wxVERTICAL</orient> + <label>Strength</label> + <object class="sizeritem"> + <flag>wxGROW</flag> + <object class="wxGauge" name="doppler_level_gauge"> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxGROW|wxLEFT|wxRIGHT|wxBOTTOM</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> <!-- Single item, doppler audio level --> + <orient>wxVERTICAL</orient> + <label>Audio Level</label> + <object class="sizeritem"> + <flag>wxGROW</flag> + <object class="wxGauge" name="doppler_audio_gauge"> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxGROW</flag> + <border>5</border> + <object class="wxBoxSizer"> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <flag>wxGROW|wxLEFT|wxRIGHT|wxBOTTOM</flag> + <border>5</border> + <option>1</option> + <object class="wxStaticBoxSizer"> <!-- Single item, absolute bearing --> + <orient>wxVERTICAL</orient> + <label>Abs. Bearing</label> + <object class="sizeritem"> + <flag>wxGROW</flag> + <object class="wxStaticText" name="doppler_absolute_bearing_text"> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxGROW|wxLEFT|wxRIGHT|wxBOTTOM</flag> + <border>5</border> + <option>1</option> + <object class="wxStaticBoxSizer"> <!-- Single item, relative bearing --> + <orient>wxVERTICAL</orient> + <label>Rel. Bearing</label> + <object class="sizeritem"> + <flag>wxGROW</flag> + <object class="wxStaticText" name="doppler_relative_bearing_text"> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + + <object class="sizeritem"> + <flag>wxGROW|wxALL</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> <!-- Each item is a Transmitter status control --> + <label>Transmitter</label> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <flag>wxGROW|wxLEFT|wxRIGHT|wxBOTTOM</flag> + <border>5</border> + <option>1</option> + <object class="wxBoxSizer"> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <flag>wxGROW|wxRIGHT</flag> + <border>5</border> + <option>1</option> + <object class="wxStaticBoxSizer"> <!-- Single item, estimated transmitter bearing --> + <orient>wxVERTICAL</orient> + <label>Est. Bearing</label> + <object class="sizeritem"> + <flag>wxGROW</flag> + <object class="wxStaticText" name="transmitter_estimated_bearing_text"> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxGROW|wxLEFT</flag> + <border>5</border> + <option>1</option> + <object class="wxStaticBoxSizer"> <!-- Single item, estimated transmitter range --> + <orient>wxVERTICAL</orient> + <label>Est. Range</label> + <object class="sizeritem"> + <flag>wxGROW</flag> + <object class="wxStaticText" name="transmitter_estimated_range_text"> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxGROW|wxLEFT|wxRIGHT|wxBOTTOM</flag> + <border>5</border> + <option>1</option> + <object class="wxBoxSizer"> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <flag>wxGROW|wxRIGHT</flag> + <border>5</border> + <option>1</option> + <object class="wxStaticBoxSizer"> <!-- Single item, actual transmitter bearing --> + <orient>wxVERTICAL</orient> + <label>Act. Bearing</label> + <object class="sizeritem"> + <flag>wxGROW</flag> + <object class="wxStaticText" name="transmitter_actual_bearing_text"> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxGROW|wxLEFT</flag> + <border>5</border> + <option>1</option> + <object class="wxStaticBoxSizer"> <!-- Single item, actual transmitter range --> + <orient>wxVERTICAL</orient> + <label>Act. Range</label> + <object class="sizeritem"> + <flag>wxGROW</flag> + <object class="wxStaticText" name="transmitter_actual_range_text"> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + + <object class="sizeritem"> + <flag>wxGROW|wxALL</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Error Histogram</label> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <object class="unknown" name="error_histogram_panel"> + <bg>#000000</bg> + <size>220,220</size> + </object> + </object> + <object class="sizeritem"> + <object class="wxBoxSizer"> + <style>wxHORIZONTAL</style> + <object class="sizeritem"> + <flag>wxALL</flag> + <border>5</border> + <object class="wxRadioBox" name="statistics_source_radiobox"> + <label>Source</label> + <style>wxRA_SPECIFY_COLS</style> + <dimension>1</dimension> + <selection>0</selection> + <item>Estimated</item> + <item>Actual</item> + </object> + </object> + <object class="sizeritem"> + <flag>wxALL</flag> + <border>5</border> + <object class="wxRadioBox" name="statistics_coords_radiobox"> + <label>Coordinates</label> + <style>wxRA_SPECIFY_COLS</style> + <dimension>1</dimension> + <selection>0</selection> + <item>Rectangular</item> + <item>Polar</item> + </object> + </object> + </object> + </object> + </object> + </object> + </object> +</object> + +<object class="wxNotebook" name="control_panel"> <!-- all user interaction goes here --> + <style>wxNB_FIXEDWIDTH|wxNB_TOP</style> + <object class="notebookpage"> + <label>Doppler</label> + <object_ref ref="doppler_control_panel"/> <!-- Attached EZ Doppler device configuration and control --> + </object> + <object class="notebookpage"> + <label>GPS</label> + <object_ref ref="gps_control_panel"/> <!-- Attached GPS device configuration and control --> + </object> + <object class="notebookpage"> + <label>Calibration</label> + <object_ref ref="calibration_control_panel"/> <!-- System calibration control --> + </object> + <object class="notebookpage"> + <label>Capture</label> + <object_ref ref="capture_control_panel"/> <!-- Bearing capture control --> + </object> + <object class="notebookpage"> + <label>Search</label> + <object_ref ref="search_control_panel"/> <!-- Search control --> + </object> + <object class="notebookpage"> + <label>Statistics</label> + <object_ref ref="statistics_control_panel"/> <!-- Statistics control --> + </object> + <object class="notebookpage"> + <label>Display</label> + <object_ref ref="display_control_panel"/> <!-- Tactical display control --> + </object> +</object> + +<object class="wxPanel" name="doppler_control_panel"> <!-- Attached EZ Doppler device configuration and control --> + <style>wxTAB_TRAVERSAL</style> + + <object class="wxBoxSizer"> <!-- Left to right controls --> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <flag>wxTOP</flag> + <border>5</border> + <object class="wxBoxSizer"> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> <!-- Start or stop doppler operation --> + <flag>wxLEFT|wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxButton" name="doppler_toggle_button"> + <label>Start</label> + </object> + </object> + <object class="sizeritem"> <!-- Reset doppler --> + <flag>wxLEFT|wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxButton" name="doppler_reset_button"> + <label>Reset</label> + </object> + </object> + <object class="sizeritem"> <!-- Check for autostart of doppler --> + <flag>wxLEFT|wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxCheckBox" name="doppler_autostart_checkbox"> + <label>Autostart</label> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Set Filtering</label> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> <!-- Set doppler filter time constant --> + <flag>wxALL|wxGROW</flag> + <border>5</border> + <object class="wxSlider" name="doppler_filter_slider"> + <style>wxSL_HORIZONTAL|wxSL_LABELS</style> + <size>250,45</size> + <min>1</min> + <value>20</value> + <max>100</max> + </object> + </object> + </object> + </object> + <object class="sizeritem"> <!-- Set doppler rotation --> + <flag>wxTOP</flag> + <border>5</border> + <object class="wxRadioBox" name="doppler_rotation_radiobox"> + <label>Set Rotation Rate (Hz)</label> + <style>wxRA_SPECIFY_ROWS</style> + <dimension>2</dimension> + <selection>5</selection> + <item>250</item> + <item>400</item> + <item>500</item> + <item>666</item> + <item>1000</item> + <item>2000</item> + </object> + </object> + </object> +</object> + +<object class="wxPanel" name="gps_control_panel"> + <style>wxTAB_TRAVERSAL</style> + <object class="wxBoxSizer"> <!-- Left to right controls --> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <flag>wxTOP</flag> + <border>5</border> + <object class="wxBoxSizer"> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> <!-- Start or stop GPS operation --> + <flag>wxALL</flag> + <border>5</border> + <object class="wxButton" name="gps_toggle_button"> + <label>Start</label> + </object> + </object> + <object class="sizeritem"> <!-- Check for autostart of GPS --> + <flag>wxALL</flag> + <border>5</border> + <object class="wxCheckBox" name="gps_autostart_checkbox"> + <label>Autostart GPS</label> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Select Port</label> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <flag>wxALL|wxGROW</flag> + <border>5</border> + <object class="wxComboBox" name="gps_device_combobox"> + <size>150,30</size> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <orient>wxVERTICAL</orient> + <label>Location</label> + <object class="sizeritem"> + <object class="wxStaticBoxSizer"> <!-- Single item, current latitude --> + <orient>wxVERTICAL</orient> + <label>Latitude</label> + <object class="sizeritem"> + <object class="wxStaticText" name="gps_latitude_text"> + <size>140,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <object class="wxStaticBoxSizer"> <!-- Single item, current latitude--> + <orient>wxVERTICAL</orient> + <label>Longitude</label> + <object class="sizeritem"> + <object class="wxStaticText" name="gps_longitude_text"> + <size>140,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <orient>wxVERTICAL</orient> + <label>Course</label> + <object class="sizeritem"> + <object class="wxStaticBoxSizer"> <!-- Single item, gps heading --> + <orient>wxVERTICAL</orient> + <label>Heading</label> + <object class="sizeritem"> + <object class="wxStaticText" name="gps_heading_text"> + <size>55,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <object class="wxStaticBoxSizer"> <!-- Single item, gps speed --> + <orient>wxVERTICAL</orient> + <label>Speed</label> + <object class="sizeritem"> + <object class="wxStaticText" name="gps_speed_text"> + <size>55,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + </object> + </object> + </object> +</object> + +<object class="wxPanel" name="calibration_control_panel"> + <style>wxTAB_TRAVERSAL</style> + <object class="wxBoxSizer"> <!-- Left to right controls --> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer" name="doppler_calibration_box"> + <orient>wxVERTICAL</orient> + <label>Doppler</label> + <object class="sizeritem"> + <object class="wxBoxSizer"> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> <!-- Perform doppler calibration --> + <flag>wxALL</flag> + <border>5</border> + <object class="wxButton" name="calibration_equalize_button"> + <label>Equalize</label> + <default>0</default> + </object> + </object> + <object class="sizeritem"> <!-- Set calibration offset to make zero --> + <flag>wxALL</flag> + <border>5</border> + <object class="wxButton" name="calibration_zero_button"> + <label>Zero</label> + <default>0</default> + </object> + </object> + </object> + </object> + + <object class="sizeritem"> + <object class="wxBoxSizer"> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <flag>wxALL</flag> + <border>5</border> + <object class="wxStaticText"> + <label>Adjust Offset:</label> + </object> + </object> + <object class="sizeritem"> <!-- Set calibration offset to make zero --> + <flag>wxALL</flag> + <border>5</border> + <object class="wxSpinButton" name="calibration_adjust_spinner"> + <style>wxSP_HORIZONTAL</style> + <value>500</value> + <min>0</min> + <max>1000</max> + </object> + </object> + </object> + </object> + + <object class="sizeritem"> + <flag>wxALL</flag> + <border>5</border> + <object class="wxCheckBox" name="calibration_all_checkbox"> + <label>Affect All Rates</label> + </object> + </object> + + </object> + </object> + + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer" name="known_transmitter_box"> + <orient>wxHORIZONTAL</orient> + <label>Known Transmitter</label> + <object class="sizeritem"> + <object class="wxBoxSizer"> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <flag>wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <orient>wxVERTICAL</orient> + <label>Latitude</label> + <object class="sizeritem"> + <object class="wxTextCtrl" name="known_transmitter_latitude_edit"> + <style>wxTE_LEFT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxTOP||wxBOTTOM|wxALIGN_CENTRE</flag> + <border>5</border> + <object class="wxButton" name="known_transmitter_update_button"> + <label>Update</label> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <object class="wxBoxSizer"> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <object class="wxStaticBoxSizer"> + <orient>wxVERTICAL</orient> + <label>Longitude</label> + <object class="sizeritem"> + <object class="wxTextCtrl" name="known_transmitter_longitude_edit"> + <style>wxTE_LEFT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxTOP|wxALIGN_CENTRE</flag> + <border>10</border> + <object class="wxCheckBox" name="known_transmitter_checkbox"> + <label>Use</label> + <default>0</default> + </object> + </object> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Bearing Error Statistics</label> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <object class="wxBoxSizer"> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <flag>wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Count</label> + <object class="sizeritem"> + <object class="wxStaticText" name="error_count_text"> + <size>75,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Mode</label> + <object class="sizeritem"> + <object class="wxStaticText" name="error_mode_text"> + <size>75,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <object class="wxBoxSizer"> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <object class="wxStaticBoxSizer"> + <label>Mean</label> + <object class="sizeritem"> + <object class="wxStaticText" name="error_mean_text"> + <size>75,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <object class="wxStaticBoxSizer"> + <label>Concen.</label> + <object class="sizeritem"> + <object class="wxStaticText" name="error_conc_text"> + <size>75,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + </object> +</object> + +<object class="wxPanel" name="capture_control_panel"> + <style>wxTAB_TRAVERSAL</style> + <object class="wxBoxSizer"> <!-- Left to right controls --> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <object class="wxPanel"> + </object> + </object> + </object> +</object> + +<object class="wxPanel" name="statistics_control_panel"> + <style>wxTAB_TRAVERSAL</style> + <object class="wxBoxSizer"> <!-- Left to right controls --> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <object class="wxPanel"> + </object> + </object> + </object> +</object> + + + +<object class="wxDialog" name="calibration_dialog"> + <title>Equalize Doppler Rotation Rates</title> + <object class="wxBoxSizer"> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <flag>wxALL</flag> + <border>10</border> + <object class="wxStaticText" name="calibration_instructions_text"> + <label>Please ensure the signal source remains fixed for\nthe duration of the test. The source does not have\nto be at a zero bearing.</label> + </object> + </object> + <object class="sizeritem"> + <flag>wxGROW|wxALL</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <orient>wxHORIZONTAL</orient> + <label>Calibration Progress...</label> + <object class="sizeritem"> + <style>wxGROW</style> + <option>1</option> + <object class="wxGauge" name="calibration_progress_gauge"> + <range>6</range> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxALL</flag> + <border>10</border> + <object class="wxStaticText" name="calibration_progress_text"> + <label>Press Start to begin, Cancel to exit...</label> + </object> + </object> + <object class="sizeritem"> + <flag>wxGROW|wxALL</flag> + <border>5</border> + <object class="wxBoxSizer"> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <flag>wxALIGN_LEFT</flag> + <object class="wxButton" name="calibration_start_button"> + <label>Start</label> + </object> + </object> + <object class="spacer"> + <size>5</size> + <option>1</option> + </object> + <object class="sizeritem"> + <flag>wxALIGN_RIGHT</flag> + <object class="wxButton" name="calibration_cancel_button"> + <label>Cancel</label> + </object> + </object> + </object> + </object> + </object> +</object> + +<object class="wxPanel" name="display_control_panel"> <!-- Tactical display options --> + <style>wxTAB_TRAVERSAL</style> + + <object class="wxBoxSizer"> <!-- Left to right controls --> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxRadioBox" name="display_orientation_radiobox"> + <label>Orientation</label> + <style>wxRA_SPECIFY_COLS</style> + <dimension>1</dimension> + <selection>0</selection> + <item>Track Up</item> + <item>North Up</item> + </object> + </object> + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + + <object class="wxStaticBoxSizer"> + <orient>wxVERTICAL</orient> + <label>Display Pointers</label> + <object class="sizeritem"> + <object class="wxCheckBox" name="display_doppler_checkbox"> + <label>Current Doppler Bearing</label> + </object> + </object> + <object class="sizeritem"> + <object class="wxCheckBox" name="display_known_checkbox"> + <label>Known Transmitter Bearing</label> + </object> + </object> + <object class="sizeritem"> + <object class="wxCheckBox" name="display_estimated_checkbox"> + <label>Estimated Bearing</label> + </object> + </object> + </object> + + </object> + </object> +</object> + +<object class="wxPanel" name="search_control_panel"> <!-- Search control options --> + <style>wxTAB_TRAVERSAL</style> + + <object class="wxBoxSizer"> <!-- Left to right controls --> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Search Log</label> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <flag>wxLEFT|wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxButton" name="search_newsave_button"> + <label>New</label> + </object> + </object> + <object class="sizeritem"> + <flag>wxALL</flag> + <border>5</border> + <object class="wxButton" name="search_openclose_button"> + <label>Open</label> + </object> + </object> + </object> + </object> + + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Capture</label> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <flag>wxTOP|wxLEFT|wxRIGHT</flag> + <border>5</border> + <object class="wxButton" name="search_toggle_button"> + <label>Auto</label> + </object> + </object> + <object class="sizeritem"> + <flag>wxALL</flag> + <border>5</border> + <object class="wxButton" name="search_once_button"> + <label>One Shot</label> + </object> + </object> + </object> + </object> + + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Estimated Location</label> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <object class="wxStaticBoxSizer"> + <label>Longitude</label> + <object class="sizeritem"> + <object class="wxStaticText" name="search_longitude_text"> + <size>150,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <object class="wxStaticBoxSizer"> + <label>Latitude</label> + <object class="sizeritem"> + <object class="wxStaticText" name="search_latitude_text"> + <size>150,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + </object> + </object> + + <object class="sizeritem"> + <flag>wxTOP|wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Search Status</label> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <object class="wxBoxSizer"> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <flag>wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Bearings</label> + <object class="sizeritem"> + <object class="wxStaticText" name="search_count_text"> + <size>100,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Status</label> + <object class="sizeritem"> + <object class="wxStaticText" name="search_status_text"> + <size>100,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + </object> + </object> + + <object class="sizeritem"> + <object class="wxBoxSizer"> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <flag>wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Mode</label> + <object class="sizeritem"> + <object class="wxStaticText" name="search_mode_text"> + <size>100,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <flag>wxRIGHT</flag> + <border>5</border> + <object class="wxStaticBoxSizer"> + <label>Mean</label> + <object class="sizeritem"> + <object class="wxStaticText" name="search_mean_text"> + <size>100,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + </object> + </object> + + <object class="sizeritem"> + <object class="wxBoxSizer"> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <object class="wxStaticBoxSizer"> + <label>Score</label> + <object class="sizeritem"> + <object class="wxStaticText" name="search_score_text"> + <size>100,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + <object class="sizeritem"> + <object class="wxStaticBoxSizer"> + <label>Distance Error</label> + <object class="sizeritem"> + <object class="wxStaticText" name="search_disterror_text"> + <size>100,22</size> + <style>wxALIGN_RIGHT</style> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + </object> +</object> + +</resource> 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 <wx/xrc/xmlres.h> +#include <wx/log.h> + +// 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 <wx/app.h> + +// 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 <wx/log.h> + +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<Sample> &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 <vector> + +class KnownTransmitter +{ +public: + KnownTransmitter(); + + void Location(const Spherical &location); + Spherical Location() const { return m_location; } + + void Clear(); + void ClearStats(); + void Calc(const std::vector<Sample> &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 <wx/log.h> + +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 <wx/string.h> + +// System level includes +#include <ctime> + +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 <wx/file.h> +#include <wx/log.h> + +// System level includes +#include <cmath> + +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 <wx/string.h> +#include <wx/event.h> + +// System level includes +#include <vector> + +// 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<Sample>& 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<Sample> 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 <wx/log.h> +#include <wx/utils.h> + +// System level includes +#include <vector> + +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<Sample> &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<Sample>&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 <wx/event.h> + +// System level includes +#include <ctime> + +// 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<Sample> &samples); + float calc_trial_error(const std::vector<Sample> &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 <wx/log.h> + +#ifdef __WIN32__ +// I hate Windows. +#else +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> +#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 <wx/arrstr.h> + +#ifdef __WIN32__ +#include <windows.h> +#else +#include <termios.h> +#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 <wx/config.h> +#include <wx/gdicmn.h> + +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 <wx/string.h> + +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 <wx/dc.h> +#include <wx/dcclient.h> +#include <wx/log.h> + +// System level includes +#include <cmath> + +// 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 <wx/panel.h> + +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 <cmath> + +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__ diff --git a/ezdop/src/host/tests/Makefile.am b/ezdop/src/host/tests/Makefile.am new file mode 100644 index 000000000..fcf19525d --- /dev/null +++ b/ezdop/src/host/tests/Makefile.am @@ -0,0 +1,31 @@ +# Copyright 2001,2002,2003,2004,2005,2006 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. + +include $(top_srcdir)/Makefile.common + +INCLUDES=$(EZDOP_INCLUDES) + +noinst_PROGRAMS = \ + dopper + +dopper_SOURCES = \ + dopper.cc + +dopper_LDADD = \ + $(top_builddir)/ezdop/src/host/ezdop/libezdop.la diff --git a/ezdop/src/host/tests/dopper.cc b/ezdop/src/host/tests/dopper.cc new file mode 100644 index 000000000..a7e79c53d --- /dev/null +++ b/ezdop/src/host/tests/dopper.cc @@ -0,0 +1,99 @@ +/* + * Copyright 2006 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. + */ + +#include <cstdio> +#include <ezdop.h> + +int chunks = 3600; +const static int samples = 250; +int rate = 250; + +static complex<float> buffer[samples]; + +int main(int argc, char *argv) +{ + ezdop *dop = new ezdop(); + + printf("Initializing EZDOP..."); + if (dop->init()) + printf("done.\n"); + else + printf("failed.\n"); + printf("EZDOP reports %s.\n", dop->is_online() ? "online" : "offline"); + + printf("Setting EZDOP rate..."); + if (dop->set_rate(rate)) + printf("done.\n"); + else + printf("failed.\n"); + + printf("Telling EZDOP to rotate..."); + if (dop->rotate()) + printf("done.\n"); + else + printf("failed.\n"); + + printf("Telling EZDOP to stream..."); + if (dop->stream()) + printf("done.\n"); + else + printf("failed.\n"); + + for (int i = 0; i < chunks; i++) { + printf("Asking EZDOP for %i samples...", samples); + int rd = dop->read_iq(buffer, samples); + printf("got %i --- ", rd); + if (rd != samples) + printf("*****\n"); + + complex<float> average = complex<float>(0.0, 0.0); + for (int j = 0; j < rd; j++) { + average += buffer[j]; + } + + float I = average.real()/rd; + float Q = average.imag()/rd; + float M = std::sqrt(I*I+Q*Q); + float dbfs = 20*std::log(M/1.4142135); + + printf("I=%f Q=%f M=%f dbfs=%f\n", I, Q, M, dbfs); + } + + printf("Telling EZDOP to stop streaming..."); + if (dop->stop_streaming()) + printf("done.\n"); + else + printf("failed.\n"); + + printf("Telling EZDOP to stop stop rotating..."); + if (dop->stop_rotating()) + printf("done.\n"); + else + printf("failed.\n"); + + printf("Releasing EZDOP..."); + if (dop->finish()) + printf("done.\n"); + else + printf("failed.\n"); + + delete dop; +} |