diff options
Diffstat (limited to 'altosui')
| -rw-r--r-- | altosui/Altos.java | 34 | ||||
| -rw-r--r-- | altosui/AltosFlightUI.java | 14 | ||||
| -rw-r--r-- | altosui/AltosLog.java | 1 | ||||
| -rw-r--r-- | altosui/AltosPreferences.java | 2 | ||||
| -rw-r--r-- | altosui/AltosScanUI.java | 467 | ||||
| -rw-r--r-- | altosui/AltosSerial.java | 10 | ||||
| -rw-r--r-- | altosui/AltosSiteMap.java | 136 | ||||
| -rw-r--r-- | altosui/AltosSiteMapPreload.java | 325 | ||||
| -rw-r--r-- | altosui/AltosSiteMapTile.java | 8 | ||||
| -rw-r--r-- | altosui/AltosTelemetryReader.java | 4 | ||||
| -rw-r--r-- | altosui/AltosTelemetryRecordConfiguration.java | 2 | ||||
| -rw-r--r-- | altosui/AltosTelemetryRecordLegacy.java | 37 | ||||
| -rw-r--r-- | altosui/AltosTelemetryRecordRaw.java | 7 | ||||
| -rw-r--r-- | altosui/AltosUI.java | 23 | ||||
| -rw-r--r-- | altosui/Makefile.am | 2 | 
15 files changed, 982 insertions, 90 deletions
| 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,Integer>	string_to_state = new HashMap<String,Integer>(); @@ -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 <keithp@keithp.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; 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<AltosScanResult> implements ListModel { +	 +	LinkedList<ListDataListener>	listeners = new LinkedList<ListDataListener>(); + +	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<LinkedBlockingQueue<AltosLine>> ();  		reply_queue = new LinkedBlockingQueue<AltosLine> ();  		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 <keithp@keithp.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; 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 \ | 
