From: Keith Packard Date: Sun, 17 Jul 2011 15:17:44 +0000 (-0700) Subject: Merge branch 'preload-maps' X-Git-Tag: 0.9.4.5~18 X-Git-Url: https://git.gag.com/?p=fw%2Faltos;a=commitdiff_plain;h=f7cd8317bf78ece334e1ceb0263b875ca43bbbd2;hp=51796e2f1ebce3ee8dc1ac90648381410c1379ee Merge branch 'preload-maps' --- diff --git a/altosui/Altos.java b/altosui/Altos.java index 96263797..8d5916ad 100644 --- a/altosui/Altos.java +++ b/altosui/Altos.java @@ -70,11 +70,23 @@ public class Altos { /* Telemetry modes */ static final int ao_telemetry_off = 0; - static final int ao_telemetry_legacy = 1; - static final int ao_telemetry_split = 2; + static final int ao_telemetry_min = 1; + static final int ao_telemetry_standard = 1; + static final int ao_telemetry_0_9 = 2; + static final int ao_telemetry_0_8 = 3; + static final int ao_telemetry_max = 3; + + static final String[] ao_telemetry_name = { + "Off", "Standard Telemetry", "TeleMetrum v0.9", "TeleMetrum v0.8" + }; + + static final int ao_telemetry_standard_len = 32; + static final int ao_telemetry_0_9_len = 95; + static final int ao_telemetry_0_8_len = 94; - static final int ao_telemetry_split_len = 32; - static final int ao_telemetry_legacy_len = 95; + static final int[] ao_telemetry_len = { + 0, 32, 95, 94 + }; static HashMap string_to_state = new HashMap(); @@ -103,6 +115,20 @@ public class Altos { map_initialized = true; } + static int telemetry_len(int telemetry) { + if (telemetry <= ao_telemetry_max) + return ao_telemetry_len[telemetry]; + throw new IllegalArgumentException(String.format("Invalid telemetry %d", + telemetry)); + } + + static String telemetry_name(int telemetry) { + if (telemetry <= ao_telemetry_max) + return ao_telemetry_name[telemetry]; + throw new IllegalArgumentException(String.format("Invalid telemetry %d", + telemetry)); + } + static String[] state_to_string = { "startup", "idle", diff --git a/altosui/AltosFlightUI.java b/altosui/AltosFlightUI.java index 9536c4bb..04bfc90d 100644 --- a/altosui/AltosFlightUI.java +++ b/altosui/AltosFlightUI.java @@ -156,14 +156,14 @@ public class AltosFlightUI extends JFrame implements AltosFlightDisplay { // Telemetry format menu telemetries = new JComboBox(); - telemetries.addItem("Original TeleMetrum Telemetry"); - telemetries.addItem("Standard AltOS Telemetry"); - int telemetry = 1; - telemetry = AltosPreferences.telemetry(serial); - if (telemetry > Altos.ao_telemetry_split) - telemetry = Altos.ao_telemetry_split; + for (int i = 1; i <= Altos.ao_telemetry_max; i++) + telemetries.addItem(Altos.telemetry_name(i)); + int telemetry = AltosPreferences.telemetry(serial); + if (telemetry <= Altos.ao_telemetry_off || + telemetry > Altos.ao_telemetry_max) + telemetry = Altos.ao_telemetry_standard; telemetries.setSelectedIndex(telemetry - 1); - telemetries.setMaximumRowCount(2); + telemetries.setMaximumRowCount(Altos.ao_telemetry_max); telemetries.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int telemetry = telemetries.getSelectedIndex() + 1; diff --git a/altosui/AltosLog.java b/altosui/AltosLog.java index 855ee2b4..6157a656 100644 --- a/altosui/AltosLog.java +++ b/altosui/AltosLog.java @@ -88,7 +88,6 @@ class AltosLog implements Runnable { close_log_file(); serial = telem.serial; flight = telem.flight; - System.out.printf("Opening telem %d %d\n", serial, flight); open(telem); } previous = telem; diff --git a/altosui/AltosPreferences.java b/altosui/AltosPreferences.java index 5029aff6..c8dee743 100644 --- a/altosui/AltosPreferences.java +++ b/altosui/AltosPreferences.java @@ -216,7 +216,7 @@ class AltosPreferences { if (telemetries.containsKey(serial)) return telemetries.get(serial); int telemetry = preferences.getInt(String.format(telemetryPreferenceFormat, serial), - Altos.ao_telemetry_split); + Altos.ao_telemetry_standard); telemetries.put(serial, telemetry); return telemetry; } diff --git a/altosui/AltosScanUI.java b/altosui/AltosScanUI.java new file mode 100644 index 00000000..96cab73b --- /dev/null +++ b/altosui/AltosScanUI.java @@ -0,0 +1,467 @@ +/* + * Copyright © 2011 Keith Packard + * + * 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; version 2 of the License. + * + * 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. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; + +class AltosScanResult { + String callsign; + int serial; + int flight; + int channel; + int telemetry; + + boolean interrupted = false; + + public String toString() { + return String.format("%-9.9s serial %-4d flight %-4d (channel %-2d %s)", + callsign, serial, flight, channel, Altos.telemetry_name(telemetry)); + } + + public String toShortString() { + return String.format("%s %d %d %d %d", + callsign, serial, flight, channel, telemetry); + } + + public AltosScanResult(String in_callsign, int in_serial, + int in_flight, int in_channel, int in_telemetry) { + callsign = in_callsign; + serial = in_serial; + flight = in_flight; + channel = in_channel; + telemetry = in_telemetry; + } + + public boolean equals(AltosScanResult other) { + return (callsign.equals(other.callsign) && + serial == other.serial && + flight == other.flight && + channel == other.channel && + telemetry == other.telemetry); + } +} + +class AltosScanResults extends LinkedList implements ListModel { + + LinkedList listeners = new LinkedList(); + + public boolean add(AltosScanResult r) { + for (AltosScanResult old : this) + if (old.equals(r)) + return true; + + super.add(r); + ListDataEvent de = new ListDataEvent(this, + ListDataEvent.INTERVAL_ADDED, + this.size() - 2, this.size() - 1); + for (ListDataListener l : listeners) + l.contentsChanged(de); + return true; + } + + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + public AltosScanResult getElementAt(int i) { + return this.get(i); + } + + public int getSize() { + return this.size(); + } +} + +public class AltosScanUI + extends JDialog + implements ActionListener +{ + AltosUI owner; + AltosDevice device; + AltosTelemetryReader reader; + private JList list; + private JLabel scanning_label; + private JButton cancel_button; + private JButton monitor_button; + javax.swing.Timer timer; + AltosScanResults results = new AltosScanResults(); + + int telemetry; + int channel; + + final static int timeout = 1200; + TelemetryHandler handler; + Thread thread; + + void scan_exception(Exception e) { + if (e instanceof FileNotFoundException) { + JOptionPane.showMessageDialog(owner, + String.format("Cannot open device \"%s\"", + device.toShortString()), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof AltosSerialInUseException) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof IOException) { + IOException ee = (IOException) e; + JOptionPane.showMessageDialog(owner, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } else { + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + } + close(); + } + + class TelemetryHandler implements Runnable { + + public void run() { + + boolean interrupted = false; + + try { + for (;;) { + try { + AltosRecord record = reader.read(); + if (record == null) + continue; + if ((record.seen & AltosRecord.seen_flight) != 0) { + final AltosScanResult result = new AltosScanResult(record.callsign, + record.serial, + record.flight, + channel, + telemetry); + Runnable r = new Runnable() { + public void run() { + results.add(result); + } + }; + SwingUtilities.invokeLater(r); + } + } catch (ParseException pp) { + } catch (AltosCRCException ce) { + } + } + } catch (InterruptedException ee) { + interrupted = true; + } catch (IOException ie) { + } finally { + reader.close(interrupted); + } + } + } + + void set_label() { + scanning_label.setText(String.format("Scanning: channel %d %s", + channel, + Altos.telemetry_name(telemetry))); + } + + void next() { + reader.serial.set_monitor(false); + try { + Thread.sleep(100); + } catch (InterruptedException ie){ + } + ++channel; + if (channel > 9) { + channel = 0; + ++telemetry; + if (telemetry > Altos.ao_telemetry_max) + telemetry = Altos.ao_telemetry_min; + reader.serial.set_telemetry(telemetry); + } + reader.serial.set_channel(channel); + set_label(); + reader.serial.set_monitor(true); + } + + + void close() { + if (thread != null && thread.isAlive()) { + thread.interrupt(); + try { + thread.join(); + } catch (InterruptedException ie) {} + } + thread = null; + if (timer != null) + timer.stop(); + setVisible(false); + dispose(); + } + + void tick_timer() { + next(); + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("cancel")) + close(); + + if (cmd.equals("tick")) + tick_timer(); + + if (cmd.equals("monitor")) { + close(); + AltosScanResult r = (AltosScanResult) (list.getSelectedValue()); + if (r != null) { + if (device != null) { + if (reader != null) { + reader.set_telemetry(r.telemetry); + reader.set_channel(r.channel); + owner.telemetry_window(device); + } + } + } + } + } + + /* A window listener to catch closing events and tell the config code */ + class ConfigListener extends WindowAdapter { + AltosScanUI ui; + + public ConfigListener(AltosScanUI this_ui) { + ui = this_ui; + } + + public void windowClosing(WindowEvent e) { + ui.actionPerformed(new ActionEvent(e.getSource(), + ActionEvent.ACTION_PERFORMED, + "close")); + } + } + + private boolean open() { + device = AltosDeviceDialog.show(owner, Altos.product_basestation); + if (device == null) + return false; + try { + reader = new AltosTelemetryReader(device); + reader.serial.set_channel(channel); + reader.serial.set_telemetry(telemetry); + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + } + reader.flush(); + handler = new TelemetryHandler(); + thread = new Thread(handler); + thread.start(); + return true; + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(owner, + String.format("Cannot open device \"%s\"", + device.toShortString()), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } catch (IOException ee) { + JOptionPane.showMessageDialog(owner, + device.toShortString(), + "Unkonwn I/O error", + JOptionPane.ERROR_MESSAGE); + } + if (reader != null) + reader.close(false); + return false; + } + + public AltosScanUI(AltosUI in_owner) { + + owner = in_owner; + + channel = 0; + telemetry = Altos.ao_telemetry_min; + + if (!open()) + return; + + Container pane = getContentPane(); + GridBagConstraints c = new GridBagConstraints(); + Insets i = new Insets(4,4,4,4); + + timer = new javax.swing.Timer(timeout, this); + timer.setActionCommand("tick"); + timer.restart(); + + owner = in_owner; + + pane.setLayout(new GridBagLayout()); + + scanning_label = new JLabel("Scanning:"); + + set_label(); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + c.anchor = GridBagConstraints.CENTER; + + pane.add(scanning_label, c); + + list = new JList(results) { + //Subclass JList to workaround bug 4832765, which can cause the + //scroll pane to not let the user easily scroll up to the beginning + //of the list. An alternative would be to set the unitIncrement + //of the JScrollBar to a fixed value. You wouldn't get the nice + //aligned scrolling, but it should work. + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, + int direction) { + int row; + if (orientation == SwingConstants.VERTICAL && + direction < 0 && (row = getFirstVisibleIndex()) != -1) { + Rectangle r = getCellBounds(row, row); + if ((r.y == visibleRect.y) && (row != 0)) { + Point loc = r.getLocation(); + loc.y--; + int prevIndex = locationToIndex(loc); + Rectangle prevR = getCellBounds(prevIndex, prevIndex); + + if (prevR == null || prevR.y >= r.y) { + return 0; + } + return prevR.height; + } + } + return super.getScrollableUnitIncrement( + visibleRect, orientation, direction); + } + }; + + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + list.setLayoutOrientation(JList.HORIZONTAL_WRAP); + list.setVisibleRowCount(-1); + + list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + monitor_button.doClick(); //emulate button click + } + } + }); + JScrollPane listScroller = new JScrollPane(list); + listScroller.setPreferredSize(new Dimension(400, 80)); + listScroller.setAlignmentX(LEFT_ALIGNMENT); + + //Create a container so that we can add a title around + //the scroll pane. Can't add a title directly to the + //scroll pane because its background would be white. + //Lay out the label and scroll pane from top to bottom. + JPanel listPane = new JPanel(); + listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS)); + + JLabel label = new JLabel("Select Device"); + label.setLabelFor(list); + listPane.add(label); + listPane.add(Box.createRigidArea(new Dimension(0,5))); + listPane.add(listScroller); + listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 2; + c.anchor = GridBagConstraints.CENTER; + + pane.add(listPane, c); + + cancel_button = new JButton("Cancel"); + cancel_button.addActionListener(this); + cancel_button.setActionCommand("cancel"); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(cancel_button, c); + + monitor_button = new JButton("Monitor"); + monitor_button.addActionListener(this); + monitor_button.setActionCommand("monitor"); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(monitor_button, c); + + pack(); + setLocationRelativeTo(owner); + + addWindowListener(new ConfigListener(this)); + + setVisible(true); + } +} \ No newline at end of file diff --git a/altosui/AltosSerial.java b/altosui/AltosSerial.java index 3666cb41..2e8ce870 100644 --- a/altosui/AltosSerial.java +++ b/altosui/AltosSerial.java @@ -326,13 +326,7 @@ public class AltosSerial implements Runnable { } private int telemetry_len() { - switch (telemetry) { - case 1: - default: - return Altos.ao_telemetry_legacy_len; - case 2: - return Altos.ao_telemetry_split_len; - } + return Altos.telemetry_len(telemetry); } public void set_channel(int in_channel) { @@ -404,7 +398,7 @@ public class AltosSerial implements Runnable { line = ""; monitor_mode = false; frame = null; - telemetry = Altos.ao_telemetry_split; + telemetry = Altos.ao_telemetry_standard; monitors = new LinkedList> (); reply_queue = new LinkedBlockingQueue (); open(); diff --git a/altosui/AltosSiteMap.java b/altosui/AltosSiteMap.java index 7575c10e..188902e9 100644 --- a/altosui/AltosSiteMap.java +++ b/altosui/AltosSiteMap.java @@ -97,6 +97,8 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { int zoom; double scale_x, scale_y; + int radius; /* half width/height of tiles to load */ + private Point2D.Double pt(double lat, double lng) { return pt(new LatLng(lat, lng), scale_x, scale_y); } @@ -144,23 +146,38 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { // nothing } - private void bgLoadMap(final AltosSiteMapTile tile, - final File pngfile, final String pngurl) + private void loadMap(final AltosSiteMapTile tile, + File pngfile, String pngurl) { - //System.out.printf("Loading/fetching map %s\n", pngfile); - Thread thread = new Thread() { - public void run() { - ImageIcon res; - res = AltosSiteMapCache.fetchAndLoadMap(pngfile, pngurl); - if (res != null) { - tile.loadMap(res); - } else { - System.out.printf("# Failed to fetch file %s\n", pngfile); - System.out.printf(" wget -O '%s' ''\n", pngfile, pngurl); - } - } - }; - thread.start(); + final ImageIcon res = AltosSiteMapCache.fetchAndLoadMap(pngfile, pngurl); + if (res != null) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + tile.loadMap(res); + } + }); + } else { + System.out.printf("# Failed to fetch file %s\n", pngfile); + System.out.printf(" wget -O '%s' '%s'\n", pngfile, pngurl); + } + } + + File pngfile; + String pngurl; + + public int prefetchMap(int x, int y) { + LatLng map_latlng = latlng( + -centre.x + x*px_size + px_size/2, + -centre.y + y*px_size + px_size/2); + pngfile = MapFile(map_latlng.lat, map_latlng.lng, zoom); + pngurl = MapURL(map_latlng.lat, map_latlng.lng, zoom); + if (pngfile.exists()) { + return 1; + } else if (AltosSiteMapCache.fetchMap(pngfile, pngurl)) { + return 0; + } else { + return -1; + } } public static void prefetchMaps(double lat, double lng, int w, int h) { @@ -172,42 +189,58 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { int dx = -w/2, dy = -h/2; for (int y = dy; y < h+dy; y++) { for (int x = dx; x < w+dx; x++) { - LatLng map_latlng = asm.latlng( - -asm.centre.x + x*px_size + px_size/2, - -asm.centre.y + y*px_size + px_size/2); - File pngfile = asm.MapFile(map_latlng.lat, map_latlng.lng); - String pngurl = asm.MapURL(map_latlng.lat, map_latlng.lng); - if (pngfile.exists()) { - System.out.printf("Already have %s\n", pngfile); - } else if (AltosSiteMapCache.fetchMap(pngfile, pngurl)) { - System.out.printf("Fetched map %s\n", pngfile); - } else { - System.out.printf("# Failed to fetch file %s\n", pngfile); - System.out.printf(" wget -O '%s' ''\n", pngfile, pngurl); + int r = asm.prefetchMap(x, y); + switch (r) { + case 1: + System.out.printf("Already have %s\n", asm.pngfile); + break; + case 0: + System.out.printf("Fetched map %s\n", asm.pngfile); + break; + case -1: + System.out.printf("# Failed to fetch file %s\n", asm.pngfile); + System.out.printf(" wget -O '%s' ''\n", asm.pngfile, asm.pngurl); + break; } } } } - private void initMap(AltosSiteMapTile tile, Point offset) { + public String initMap(Point offset) { + AltosSiteMapTile tile = mapTiles.get(offset); Point2D.Double coord = tileCoordOffset(offset); LatLng map_latlng = latlng(px_size/2-coord.x, px_size/2-coord.y); - File pngfile = MapFile(map_latlng.lat, map_latlng.lng); - String pngurl = MapURL(map_latlng.lat, map_latlng.lng); - bgLoadMap(tile, pngfile, pngurl); + File pngfile = MapFile(map_latlng.lat, map_latlng.lng, zoom); + String pngurl = MapURL(map_latlng.lat, map_latlng.lng, zoom); + loadMap(tile, pngfile, pngurl); + return pngfile.toString(); } - private void initMaps(double lat, double lng) { - centre = getBaseLocation(lat, lng); - + public void setBaseLocation(double lat, double lng) { for (Point k : mapTiles.keySet()) { - initMap(mapTiles.get(k), k); + AltosSiteMapTile tile = mapTiles.get(k); + tile.clearMap(); } + + centre = getBaseLocation(lat, lng); + scrollRocketToVisible(pt(lat,lng)); } - private File MapFile(double lat, double lng) { + private void initMaps(double lat, double lng) { + setBaseLocation(lat, lng); + + Thread thread = new Thread() { + public void run() { + for (Point k : mapTiles.keySet()) + initMap(k); + } + }; + thread.start(); + } + + private static File MapFile(double lat, double lng, int zoom) { char chlat = lat < 0 ? 'S' : 'N'; char chlng = lng < 0 ? 'W' : 'E'; if (lat < 0) lat = -lat; @@ -217,13 +250,18 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { chlat, lat, chlng, lng, zoom)); } - private String MapURL(double lat, double lng) { + private static String MapURL(double lat, double lng, int zoom) { return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=hybrid&format=png32", lat, lng, zoom, px_size, px_size); } boolean initialised = false; Point2D.Double last_pt = null; int last_state = -1; + + public void show(double lat, double lon) { + initMaps(lat, lon); + scrollRocketToVisible(pt(lat, lon)); + } public void show(final AltosState state, final int crc_errors) { // if insufficient gps data, nothing to update if (!state.gps.locked && state.gps.nsat < 4) @@ -268,7 +306,7 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { AltosSiteMapTile tile = createTile(offset); tile.show(state, crc_errors, lref, ref); - initMap(tile, offset); + initMap(offset); finishTileLater(tile, offset); } @@ -298,13 +336,13 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { } private void ensureTilesAround(Point base_offset) { - for (int x = -1; x <= 1; x++) { - for (int y = -1; y <= 1; y++) { + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { Point offset = new Point(base_offset.x + x, base_offset.y + y); if (mapTiles.containsKey(offset)) continue; AltosSiteMapTile tile = createTile(offset); - initMap(tile, offset); + initMap(offset); finishTileLater(tile, offset); } } @@ -369,19 +407,25 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { JComponent comp = new JComponent() { }; private GridBagLayout layout = new GridBagLayout(); - public AltosSiteMap() { + public AltosSiteMap(int in_radius) { + radius = in_radius; + GrabNDrag scroller = new GrabNDrag(comp); comp.setLayout(layout); - for (int x = -1; x <= 1; x++) { - for (int y = -1; y <= 1; y++) { + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { Point offset = new Point(x, y); AltosSiteMapTile t = createTile(offset); addTileAt(t, offset); } } setViewportView(comp); - setPreferredSize(new Dimension(500,200)); + setPreferredSize(new Dimension(500,500)); + } + + public AltosSiteMap() { + this(1); } } diff --git a/altosui/AltosSiteMapPreload.java b/altosui/AltosSiteMapPreload.java new file mode 100644 index 00000000..876c14ac --- /dev/null +++ b/altosui/AltosSiteMapPreload.java @@ -0,0 +1,325 @@ +/* + * Copyright © 2011 Keith Packard + * + * 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; version 2 of the License. + * + * 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. + */ + +package altosui; + +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.MouseInputAdapter; +import javax.imageio.ImageIO; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.lang.Math; +import java.awt.geom.Point2D; +import java.awt.geom.Line2D; + +class AltosMapPos extends Box { + AltosUI owner; + JLabel label; + JComboBox hemi; + JTextField deg; + JLabel deg_label; + JTextField min; + JLabel min_label; + + public void set_value(double new_value) { + double d, m; + int h; + + h = 0; + if (new_value < 0) { + h = 1; + new_value = -new_value; + } + d = Math.floor(new_value); + deg.setText(String.format("%3.0f", d)); + m = (new_value - d) * 60.0; + min.setText(String.format("%7.4f", m)); + hemi.setSelectedIndex(h); + } + + public double get_value() throws NumberFormatException { + int h = hemi.getSelectedIndex(); + String d_t = deg.getText(); + String m_t = min.getText(); + double d, m, v; + try { + d = Double.parseDouble(d_t); + } catch (NumberFormatException ne) { + JOptionPane.showMessageDialog(owner, + String.format("Invalid degrees \"%s\"", + d_t), + "Invalid number", + JOptionPane.ERROR_MESSAGE); + throw ne; + } + try { + if (m_t.equals("")) + m = 0; + else + m = Double.parseDouble(m_t); + } catch (NumberFormatException ne) { + JOptionPane.showMessageDialog(owner, + String.format("Invalid minutes \"%s\"", + m_t), + "Invalid number", + JOptionPane.ERROR_MESSAGE); + throw ne; + } + v = d + m/60.0; + if (h == 1) + v = -v; + return v; + } + + public AltosMapPos(AltosUI in_owner, + String label_value, + String[] hemi_names, + double default_value) { + super(BoxLayout.X_AXIS); + owner = in_owner; + label = new JLabel(label_value); + hemi = new JComboBox(hemi_names); + hemi.setEditable(false); + deg = new JTextField("000"); + deg_label = new JLabel("°"); + min = new JTextField("00.0000"); + min_label = new JLabel("'"); + set_value(default_value); + deg.setMinimumSize(deg.getPreferredSize()); + min.setMinimumSize(min.getPreferredSize()); + add(label); + add(Box.createRigidArea(new Dimension(5, 0))); + add(hemi); + add(Box.createRigidArea(new Dimension(5, 0))); + add(deg); + add(Box.createRigidArea(new Dimension(5, 0))); + add(deg_label); + add(Box.createRigidArea(new Dimension(5, 0))); + add(min); + add(Box.createRigidArea(new Dimension(5, 0))); + add(min_label); + } +} + +public class AltosSiteMapPreload extends JDialog implements ActionListener { + AltosUI owner; + AltosSiteMap map; + + AltosMapPos lat; + AltosMapPos lon; + + JProgressBar pbar; + + final static int radius = 4; + final static int width = (radius * 2 + 1); + final static int height = (radius * 2 + 1); + + JToggleButton load_button; + boolean loading; + JButton close_button; + + static final String[] lat_hemi_names = { "N", "S" }; + static final String[] lon_hemi_names = { "E", "W" }; + + class updatePbar implements Runnable { + int n; + String s; + + public updatePbar(int x, int y, String in_s) { + n = (x + radius) + (y + radius) * width + 1; + s = in_s; + } + + public void run() { + pbar.setValue(n); + pbar.setString(s); + if (n < width * height) { + pbar.setValue(n); + pbar.setString(s); + } else { + pbar.setValue(0); + pbar.setString(""); + load_button.setSelected(false); + loading = false; + } + } + } + + class bgLoad extends Thread { + + AltosSiteMap map; + + public bgLoad(AltosSiteMap in_map) { + map = in_map; + } + + public void run() { + for (int y = -map.radius; y <= map.radius; y++) { + for (int x = -map.radius; x <= map.radius; x++) { + String pngfile; + pngfile = map.initMap(new Point(x,y)); + SwingUtilities.invokeLater(new updatePbar(x, y, pngfile)); + } + } + } + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("close")) + setVisible(false); + + if (cmd.equals("load")) { + if (!loading) { + try { + final double latitude = lat.get_value(); + final double longitude = lon.get_value(); + map.setBaseLocation(latitude,longitude); + loading = true; + bgLoad thread = new bgLoad(map); + thread.start(); + } catch (NumberFormatException ne) { + load_button.setSelected(false); + } + } + } + } + + public AltosSiteMapPreload(AltosUI in_owner) { + owner = in_owner; + + Container pane = getContentPane(); + GridBagConstraints c = new GridBagConstraints(); + Insets i = new Insets(4,4,4,4); + + pane.setLayout(new GridBagLayout()); + + map = new AltosSiteMap(4); + + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + c.anchor = GridBagConstraints.CENTER; + + pane.add(map, c); + + pbar = new JProgressBar(); + pbar.setMinimum(0); + pbar.setMaximum(width * height); + pbar.setValue(0); + pbar.setString(""); + pbar.setStringPainted(true); + + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 2; + + pane.add(pbar, c); + + lat = new AltosMapPos(owner, + "Latitude:", + lat_hemi_names, + 37.167833333); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 0; + c.weighty = 0; + + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(lat, c); + + lon = new AltosMapPos(owner, + "Longitude:", + lon_hemi_names, + -97.73975); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 0; + c.weighty = 0; + + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(lon, c); + + load_button = new JToggleButton("Load Map"); + load_button.addActionListener(this); + load_button.setActionCommand("load"); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 0; + c.gridy = 3; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(load_button, c); + + close_button = new JButton("Close"); + close_button.addActionListener(this); + close_button.setActionCommand("close"); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 1; + c.gridy = 3; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(close_button, c); + + pack(); + setLocationRelativeTo(owner); + setVisible(true); + } +} \ No newline at end of file diff --git a/altosui/AltosSiteMapTile.java b/altosui/AltosSiteMapTile.java index 8301f42b..66da7c54 100644 --- a/altosui/AltosSiteMapTile.java +++ b/altosui/AltosSiteMapTile.java @@ -35,11 +35,16 @@ public class AltosSiteMapTile extends JLayeredPane { JLabel mapLabel; JLabel draw; Graphics2D g2d; + int px_size; public void loadMap(ImageIcon icn) { mapLabel.setIcon(icn); } + public void clearMap() { + fillLabel(mapLabel, Color.GRAY, px_size); + } + static Color stateColors[] = { Color.WHITE, // startup Color.WHITE, // idle @@ -90,7 +95,8 @@ public class AltosSiteMapTile extends JLayeredPane { return g; } - public AltosSiteMapTile(int px_size) { + public AltosSiteMapTile(int in_px_size) { + px_size = in_px_size; setPreferredSize(new Dimension(px_size, px_size)); mapLabel = new JLabel(); diff --git a/altosui/AltosTelemetryReader.java b/altosui/AltosTelemetryReader.java index 18f17841..23524b2c 100644 --- a/altosui/AltosTelemetryReader.java +++ b/altosui/AltosTelemetryReader.java @@ -39,6 +39,10 @@ class AltosTelemetryReader extends AltosFlightReader { return next; } + void flush() { + telem.clear(); + } + void close(boolean interrupted) { serial.remove_monitor(telem); log.close(); diff --git a/altosui/AltosTelemetryRecordConfiguration.java b/altosui/AltosTelemetryRecordConfiguration.java index 98dc6ab9..b029d120 100644 --- a/altosui/AltosTelemetryRecordConfiguration.java +++ b/altosui/AltosTelemetryRecordConfiguration.java @@ -57,7 +57,7 @@ public class AltosTelemetryRecordConfiguration extends AltosTelemetryRecordRaw { next.callsign = callsign; next.firmware_version = version; - next.seen |= AltosRecord.seen_deploy; + next.seen |= AltosRecord.seen_deploy | AltosRecord.seen_flight; return next; } diff --git a/altosui/AltosTelemetryRecordLegacy.java b/altosui/AltosTelemetryRecordLegacy.java index e3751ee7..f59027ab 100644 --- a/altosui/AltosTelemetryRecordLegacy.java +++ b/altosui/AltosTelemetryRecordLegacy.java @@ -385,24 +385,25 @@ public class AltosTelemetryRecordLegacy extends AltosRecord implements AltosTele */ int[] bytes; + int adjust; private int int8(int i) { - return Altos.int8(bytes, i + 1); + return Altos.int8(bytes, i + 1 + adjust); } private int uint8(int i) { - return Altos.uint8(bytes, i + 1); + return Altos.uint8(bytes, i + 1 + adjust); } private int int16(int i) { - return Altos.int16(bytes, i + 1); + return Altos.int16(bytes, i + 1 + adjust); } private int uint16(int i) { - return Altos.uint16(bytes, i + 1); + return Altos.uint16(bytes, i + 1 + adjust); } private int uint32(int i) { - return Altos.uint32(bytes, i + 1); + return Altos.uint32(bytes, i + 1 + adjust); } private String string(int i, int l) { - return Altos.string(bytes, i + 1, l); + return Altos.string(bytes, i + 1 + adjust, l); } static final int AO_GPS_NUM_SAT_MASK = (0xf << 0); @@ -413,23 +414,20 @@ public class AltosTelemetryRecordLegacy extends AltosRecord implements AltosTele static final int AO_GPS_DATE_VALID = (1 << 6); static final int AO_GPS_COURSE_VALID = (1 << 7); - static class theLock extends Object { - } - static public theLock lockObject = new theLock(); public AltosTelemetryRecordLegacy(int[] in_bytes, int in_rssi, int in_status) { bytes = in_bytes; - synchronized(lockObject) { - for (int i = 0; i < in_bytes.length - 2; i++) { - if ((i % 10) == 0) - System.out.printf("%3d:", i); - System.out.printf(" %02x", uint8(i)); - if ((i % 10) == 9 || i == in_bytes.length - 3) - System.out.printf("\n"); - } - } version = 4; + adjust = 0; + + if (bytes.length == Altos.ao_telemetry_0_8_len + 4) { + serial = uint8(0); + adjust = -1; + } else + serial = uint16(0); + + seen = seen_flight | seen_sensor | seen_temp_volt | seen_deploy; + callsign = string(62, 8); - serial = uint16(0); flight = uint16(2); rssi = in_rssi; status = in_status; @@ -470,6 +468,7 @@ public class AltosTelemetryRecordLegacy extends AltosRecord implements AltosTele if ((gps_flags & (AO_GPS_VALID|AO_GPS_RUNNING)) != 0) { gps = new AltosGPS(); + seen |= seen_gps_time | seen_gps_lat | seen_gps_lon; gps.nsat = (gps_flags & AO_GPS_NUM_SAT_MASK); gps.locked = (gps_flags & AO_GPS_VALID) != 0; gps.connected = true; diff --git a/altosui/AltosTelemetryRecordRaw.java b/altosui/AltosTelemetryRecordRaw.java index e6c4cfc8..4b34f017 100644 --- a/altosui/AltosTelemetryRecordRaw.java +++ b/altosui/AltosTelemetryRecordRaw.java @@ -72,7 +72,7 @@ public class AltosTelemetryRecordRaw implements AltosTelemetryRecord { /* length, data ..., rssi, status, checksum -- 4 bytes extra */ switch (bytes.length) { - case Altos.ao_telemetry_split_len + 4: + case Altos.ao_telemetry_standard_len + 4: int type = Altos.uint8(bytes, 4 + 1); switch (type) { case packet_type_TM_sensor: @@ -94,7 +94,10 @@ public class AltosTelemetryRecordRaw implements AltosTelemetryRecord { break; } break; - case Altos.ao_telemetry_legacy_len + 4: + case Altos.ao_telemetry_0_9_len + 4: + r = new AltosTelemetryRecordLegacy(bytes, rssi, status); + break; + case Altos.ao_telemetry_0_8_len + 4: r = new AltosTelemetryRecordLegacy(bytes, rssi, status); break; default: diff --git a/altosui/AltosUI.java b/altosui/AltosUI.java index 7bb4ba12..d8c8d61c 100644 --- a/altosui/AltosUI.java +++ b/altosui/AltosUI.java @@ -172,6 +172,21 @@ public class AltosUI extends JFrame { } }); + + b = addButton(0, 2, "Scan Channels"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ScanChannels(); + } + }); + + b = addButton(1, 2, "Load Maps"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + LoadMaps(); + } + }); + setTitle("AltOS"); pane.doLayout(); @@ -226,6 +241,14 @@ public class AltosUI extends JFrame { new AltosIgniteUI(AltosUI.this); } + void ScanChannels() { + new AltosScanUI(AltosUI.this); + } + + void LoadMaps() { + new AltosSiteMapPreload(AltosUI.this); + } + /* * Replay a flight from telemetry data */ diff --git a/altosui/Makefile.am b/altosui/Makefile.am index 6e64acab..18862d98 100644 --- a/altosui/Makefile.am +++ b/altosui/Makefile.am @@ -83,10 +83,12 @@ altosui_JAVA = \ AltosReplayReader.java \ AltosRomconfig.java \ AltosRomconfigUI.java \ + AltosScanUI.java \ AltosSerial.java \ AltosSerialInUseException.java \ AltosSerialMonitor.java \ AltosSiteMap.java \ + AltosSiteMapPreload.java \ AltosSiteMapCache.java \ AltosSiteMapTile.java \ AltosState.java \