This commit is contained in:
Cyberes 2022-01-05 16:42:46 -07:00
commit b7043d3eaf
3700 changed files with 551541 additions and 0 deletions

5
README.MD Normal file
View File

@ -0,0 +1,5 @@
# yetanotheraprsc
_A git mirror of the YAAC SVN repository_
From https://sourceforge.net/projects/yetanotheraprsc/

Binary file not shown.

Binary file not shown.

BIN
Wiki/Downloading YAAC.pdf Normal file

Binary file not shown.

BIN
Wiki/Home.pdf Normal file

Binary file not shown.

BIN
Wiki/Running YAAC.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Wiki/Welcome to YAAC.pdf Normal file

Binary file not shown.

11
update-mirror.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
svn checkout https://svn.code.sf.net/p/yetanotheraprsc/code/trunk yetanotheraprsc-code
wkhtmltopdf https://sourceforge.net/p/yetanotheraprsc/wiki/Building%20and%20Modifying%20YAAC/ "Wiki/Building and Modifying YAAC.pdf"
wkhtmltopdf https://sourceforge.net/p/yetanotheraprsc/wiki/Downloading%20YAAC/ "Wiki/Downloading YAAC.pdf"
wkhtmltopdf https://sourceforge.net/p/yetanotheraprsc/wiki/Home/ "Wiki/Home.pdf"
wkhtmltopdf https://sourceforge.net/p/yetanotheraprsc/wiki/Running%20YAAC/ "Wiki/Running YAAC.pdf"
wkhtmltopdf https://sourceforge.net/p/yetanotheraprsc/wiki/download%20maps/ "Wiki/Downloading OpenStreetMap Data for YAAC.pdf"
wkhtmltopdf https://sourceforge.net/p/yetanotheraprsc/wiki/supported%20computer%20platforms%20for%20YAAC/ "Wiki/Supported Computer Platforms for YAAC.pdf"
wkhtmltopdf https://sourceforge.net/p/yetanotheraprsc/wiki/supported%20hardware/ "Wiki/Supported Hardware for YAAC.pdf"
wkhtmltopdf https://www.ka2ddo.org/ka2ddo/YAAC.html "Wiki/Welcome to YAAC.pdf"

View File

@ -0,0 +1 @@
12

View File

@ -0,0 +1 @@
12

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE index
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Index Version 2.0//EN"
"http://java.sun.com/products/javahelp/index_2_0.dtd">
<!--
Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
This file is part of YAAC (Yet Another APRS Client).
YAAC is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
YAAC 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
and GNU Lesser General Public License along with YAAC. If not,
see <http://www.gnu.org/licenses/>.
-->
<index version="2.0">
<indexitem text="configuring YAAC">
<indexitem text="small screen" target="configure.smallscreen"/>
</indexitem>
<indexitem text="first window">
<indexitem text="small screen" target="menu.View.SmallScreen.first"/>
</indexitem>
<indexitem text="Mic-E">
<indexitem text="changing Mic-E status" target="menu.View.SmallScreen.Status"/>
</indexitem>
<indexitem text="small screen" target="menu.View.SmallScreen.view">
<indexitem text="configuring" target="configure.smallscreen"/>
<indexitem text="viewing" target="menu.View.SmallScreen"/>
</indexitem>
</index>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 B

View File

@ -0,0 +1,29 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Locate Landmark</title>
</head>
<body lang="en-US" dir="LTR">
<h1>Locate Landmark</h1>
<p>The Locate Landmarks menu choice allows you to find a named
feature in the OpenStreetMap data on the map. A popup window is
opened, displaying a text field for typing in a partial name string
match (made case-insensitive); a Java compatible regular expression may
also be used here.</p>
<img src="./locatelandmark.png" alt="locate landmark dialog"/>
<p>A second name string may be used to find intersections of two streets;
you may also search for occurrences of bridges or tunnels.</p>
<p>When the Enter key is pressed on the keyboard, all map features
within the specified distance of the current center of the map will
be checked to see if their name matches the specified match string.
All matches will be displayed by name, distance from the map center,
and type in a table in the dialog. Clicking on any row in the table
will pan and zoom the map to display the matching feature in the
center of the map.</p>
<p>Changing the string in the text field and pressing Enter again
will start another search. The search dialog can be closed with the
operating system standard window closing mark on the dialog's title
bar.</p>
</body>
</html>

View File

@ -0,0 +1,76 @@
package org.ka2ddo.yaac.gui.io;
/*
* Copyright (C) 2011-2019 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.yaac.io.PortConfig;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ResourceBundle;
/**
* This class implements the code needed to create with port configuration widgets to select
* AX.25 protocols should be permitted on a port.
* @author Andrew Pavlin, KA2DDO
*/
public class ProtocolSelectionControls implements ItemListener {
/**
* Create a generic set of controls for enabling or disabling the protocols to be supported
* over the asscoiated AX.25 port interface.
* @param msgBundle ResourceBundle for obtaining localized label translations
* @param cfg PortConfig.Cfg object to store protocol selection bits
* @return JPanel loaded with controls
*/
public static JPanel createWidgets(ResourceBundle msgBundle, PortConfig.Cfg cfg) {
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEADING));
p.add(new JLabel(msgBundle.getString("configure.tab.Ports.Protocols")));
for (int bit = 0; bit <= PortConfig.MAX_PROTOCOL_BIT; bit++) {
JCheckBox cb;
int bitNum = 1 << bit;
if (msgBundle.containsKey("configure.tab.Ports.Protocols." + bit)) {
p.add(cb = new JCheckBox(msgBundle.getString("configure.tab.Ports.Protocols." + bit), (cfg.acceptableProtocolsMask & bitNum) != 0));
cb.addItemListener(new ProtocolSelectionControls(cfg, bitNum));
}
}
return p;
}
private final PortConfig.Cfg cfg;
private final int bitMask;
private ProtocolSelectionControls(PortConfig.Cfg cfg, int bitMask) {
this.cfg = cfg;
this.bitMask = bitMask;
}
/**
* DO NOT CALL. Handle state changes of an associated JCheckBox for the protocol selection controls.
* @param e ItemEvent identifying the checkbox
*/
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
cfg.acceptableProtocolsMask |= bitMask;
} else {
cfg.acceptableProtocolsMask &= ~bitMask;
}
}
}

View File

@ -0,0 +1,191 @@
package org.ka2ddo.aprs;
/*
* Copyright (C) 2011-2019 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.util.DistanceUnit;
import org.ka2ddo.util.EnumWithImageName;
import org.ka2ddo.util.EnumWithUnits;
import org.ka2ddo.util.PressureUnit;
/**
* Symbolic codes for different types of hurricane/tropical storm information. The letter codes correspond to the
* hurricane comments in APRS position reports, as defined in the APRS protocol specification, chapter 12.
* @see PositionReport
* @author Andrew Pavlin, KA2DDO
*/
public enum HurricaneEnum implements EnumWithImageName, EnumWithUnits {
/**
* Type of severe storm.
* @see StormType
*/
STORM_TYPE(2, true, '\u007F', "images/stormtype.png", StormType.HC, 0.0F),
/**
* Sustained windspeed of storm in knots (nm/hr).
* @see DistanceUnit#NM
*/
SUSTAINED_WIND(3, true, '\0', "images/windspeed.png", DistanceUnit.NM, true, 1.0F),
/**
* Peak gust speed of wind in storm in knots (nm/hr).
* @see DistanceUnit#NM
*/
GUST(3, true, '^', "images/gust.png", DistanceUnit.NM, true, 1.0F),
/**
* Barometric pressure at center of storm in hectoPascals (millibars).
* @see PressureUnit#HPa
*/
PRESSURE(4, true, '/', "images/pressure.png", PressureUnit.HPa, 0.1F),
/**
* Radius of hurricane winds in nautical miles.
* @see DistanceUnit#NM
*/
RADIUS_HURRICANE(3, false, '>', "images/radius_hc.png", DistanceUnit.NM, 1.0F),
/**
* Radius of tropical-storm-level winds in nautical miles.
* @see DistanceUnit#NM
*/
RADIUS_TROP_STORM(3, false, '&', "images/radius_ts.png", DistanceUnit.NM, 1.0F),
/**
* Radius of gale-force winds in nautical miles.
* @see DistanceUnit#NM
*/
RADIUS_GALE(3, false, '%', "images/radius_gale.png", DistanceUnit.NM, 1.0F)
;
/**
* Number of characters for parameter identified by this enum.
*/
public final int paramLen;
final boolean required;
final char letter;
final String imgName;
final Enum<?> unit;
final boolean isUnitARate;
final float unitScaling;
/**
* Values allowed for type of storm in a hurricane position report.
*/
public enum StormType {
/**
* StormType of Hurricane.
*/
HC,
/**
* StormType of Tropical Storm.
*/
TS,
/**
* StormType of Tropical Depression.
*/
TD,
/**
* StormType of Typhoon.
*/
TY,
/**
* StormType of Tropical Cyclone
*/
TC,
/**
* StormType of Tropical Low.
*/
TL
}
private static final HurricaneEnum[] valuesArray = values();
private HurricaneEnum(int paramLen, boolean required, char letter, String imageFileName, Enum<?> unit, float unitScaling) {
this.paramLen = paramLen;
this.required = required;
this.letter = letter;
this.imgName = imageFileName;
this.unit = unit;
this.isUnitARate = false;
this.unitScaling = unitScaling;
}
private HurricaneEnum(int paramLen, boolean required, char letter, String imageFileName, Enum<?> unit, boolean isUnitARate, float unitScaling) {
this.paramLen = paramLen;
this.required = required;
this.letter = letter;
this.imgName = imageFileName;
this.unit = unit;
this.isUnitARate = isUnitARate;
this.unitScaling = unitScaling;
}
/**
* Return the relative path name of the image file for this enum value.
*
* @return path name String, or a two-character APRS symbol table ID and code
*/
public String getImagePath() {
return imgName;
}
/**
* Get the enumeration associated with the hurricane message letter code.
* @param ch letter to match against the known enums
* @return HurricaneEnum corresponding to the letter, or null if not matching any known hurricane parameter
*/
public static HurricaneEnum getEnumForLetter(char ch) {
for (HurricaneEnum e : valuesArray) {
if (ch == e.letter) {
return e;
}
}
return null;
}
/**
* Get the unit (if any) for values for this WeatherEnum,
* @return unit Enum, or null if no unit known
*/
public Enum<?> getUnit() {
return unit;
}
/**
* Indicate whether values and units should be interpreted as a rate over an appropriate time unit
* rather than a fixed quantity.
*
* @return boolean true if unit is a rate rather than a scalar
*/
public boolean isUnitARate() {
return isUnitARate;
}
/**
* Get the scaling factor to apply to the unit for values for this WeatherEnum.
* @return float scale factor to apply to unit for displaying value, or Float.NaN if no unit
*/
public float getUnitScaling() {
return unitScaling;
}
/**
* Returns a single copy of the list of WeatherEnums. Callers should ensure they do not
* modify the returned array, as this could have unpredictable consequences to other users
* of the array.
* @return array of defined WeatherEnum values in enumerated order
*/
public static HurricaneEnum[] nonClonedValues() {
return valuesArray;
}
}

View File

@ -0,0 +1,108 @@
package org.ka2ddo.yaac.osm;
/*
* Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import java.util.HashMap;
/**
* Supported types of the OSM waterway="*" attribute.
* See the <a href="http://wiki.openstreetmap.org/wiki/Key:waterway" target="OSM">definition of waterway</a>
* on the OpenStreetMap wiki.
*/
public enum Waterway implements AmenityOrWay, EnumMayHaveFlags {
user_defined(WayType.WWY_UNKNOWN),
stream(WayType.WWY_STREAM),
river(WayType.WWY_RIVER),
riverbank(WayType.WWY_RIVERBANK),
canal(WayType.WWY_CANAL),
ditch(WayType.WWY_DITCH),
drain(WayType.WWY_DITCH),
rapids(WayType.WWY_RAPIDS),
waterfall(WayType.WWY_WATERFALL),
dam(AmenityType.dam),
weir(AmenityType.dam);
private final WayType wayType;
private final AmenityType amenityType;
private final short flagMask;
private Waterway(WayType wayType) {
this.wayType = wayType;
this.amenityType = null;
this.flagMask = GenericTaggedNode.IS_WATER;
}
private Waterway(WayType wayType, short extraFlags) {
this.wayType = wayType;
this.amenityType = null;
this.flagMask = (short)(extraFlags | GenericTaggedNode.IS_WATER);
}
private Waterway(AmenityType amenityType) {
this.wayType = null;
this.amenityType = amenityType;
this.flagMask = GenericTaggedNode.IS_WATER;
}
/**
* Report the WayType for rendering this type of Way.
* @return WayType
*/
public WayType wayType() {
return wayType;
}
/**
* Get amenity type associated with this enumeration; only for types that are amenities.
*
* @return AmenityType for this type, or null if not an amenity
*/
public AmenityType getAmenityType() {
return amenityType;
}
private static HashMap<String,Waterway> enumStringToEnumMap = null;
/**
* Get the enum value associated with the string name specified.
* @param name String to convert into an Enum
* @return Waterway for the string name, or null if no match
*/
public static Waterway value(String name) {
HashMap<String,Waterway> map;
if ((map = enumStringToEnumMap) == null) {
// first call, so initialize map
map = enumStringToEnumMap = new HashMap<String,Waterway>();
for (Waterway e : values()) {
map.put(e.name(), e);
}
}
return map.get(name);
}
/**
* Indicates any flag bits implied by this enum type.
*
* @return bitmask of flags if this enum implies flags
*/
@Override
public short getFlagMask() {
return flagMask;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE helpset
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 2.0//EN"
"http://java.sun.com/products/javahelp/helpset_2_0.dtd">
<helpset version="2.0">
<!-- title -->
<title>YAAC Legacy RXTX Library Support</title>
<!-- maps -->
<maps>
<homeID>legacyrxtx</homeID>
<mapref location="LegacyRxtxHelpMap.jhm"/>
</maps>
<!-- views -->
<view mergetype="javax.help.UniteAppendMerge">
<name>TOC</name>
<label>Table Of Contents</label>
<type>javax.help.TOCView</type>
<data>LegacyRxtxHelpTOC.xml</data>
</view>
<view mergetype="javax.help.SortMerge">
<name>Index</name>
<label>Index</label>
<type>javax.help.IndexView</type>
<data>LegacyRxtxHelpIndex.xml</data>
</view>
<presentation xml:lang="en" default="true" displayviewimages="false">
<name>main window</name>
<size width="700" height="400" />
<location x="200" y="200" />
<title>YAAC - Online Help</title>
<image>icon</image>
<toolbar>
<helpaction>javax.help.BackAction</helpaction>
<helpaction>javax.help.ForwardAction</helpaction>
<helpaction>javax.help.SeparatorAction</helpaction>
<helpaction>javax.help.HomeAction</helpaction>
<helpaction>javax.help.ReloadAction</helpaction>
<helpaction>javax.help.SeparatorAction</helpaction>
<helpaction>javax.help.PrintAction</helpaction>
<helpaction>javax.help.PrintSetupAction</helpaction>
<helpaction>javax.help.SeparatorAction</helpaction>
<helpaction>javax.help.FavoritesAction</helpaction>
</toolbar>
</presentation>
</helpset>

View File

@ -0,0 +1,146 @@
package org.ka2ddo.yaac.smallscreen.gui;
/*
* Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
* This file is part of YAAC.
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.ax25.Scope;
import org.ka2ddo.yaac.YAAC;
import org.ka2ddo.yaac.ax25.Age;
import org.ka2ddo.yaac.ax25.StationState;
import org.ka2ddo.yaac.ax25.StationTracker;
import org.ka2ddo.yaac.gui.MainGui;
import org.ka2ddo.yaac.gui.NullableBooleanRenderer;
import org.ka2ddo.yaac.gui.SelectableSymbol;
import org.ka2ddo.yaac.gui.StationListTableModel;
import org.ka2ddo.yaac.gui.TablePersister;
import org.ka2ddo.yaac.gui.help.HelpAdapter;
import org.ka2ddo.yaac.gui.table.AgeRenderer;
import org.ka2ddo.yaac.gui.table.EnumCellRenderer;
import org.ka2ddo.yaac.gui.table.FastTableRowSorter;
import org.ka2ddo.yaac.gui.table.PrintableTable;
import org.ka2ddo.yaac.gui.table.SelectableSymbolEditor;
import org.ka2ddo.yaac.gui.table.SelectableSymbolRenderer;
import org.ka2ddo.yaac.gui.table.StringCellRenderer;
import org.ka2ddo.yaac.pluginapi.GuiContentType;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import static org.ka2ddo.yaac.gui.StationListViewer.DEFAULT_SORT_KEYS;
/**
* This class provides a compact version of the Station/Object list.
*/
public class StationList extends JScrollPane implements HasMenuToggleButton {
int selectedObjectRow = -1;
private JToggleButton tbShowMenu;
/**
* Create a panel for displaying the currently known stations on a small screen.
* @param smallScreen SmallScreen that wll contain this panel
*/
public StationList(SmallScreen smallScreen) {
setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
final StationListTableModel tm = new StationListTableModel(YAAC.getMsgBundle(), false);
final PrintableTable table = new PrintableTable(tm, YAAC.getMsg("StationList.printTitle"));
FastTableRowSorter<TableModel> sorter = new FastTableRowSorter<TableModel>(tm);
sorter.setSortKeys(DEFAULT_SORT_KEYS);
table.setRowSorter(sorter);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.setPreferredScrollableViewportSize(new Dimension(500, 250));
table.setDefaultRenderer(Age.class, new AgeRenderer());
table.setDefaultRenderer(Boolean.class, new NullableBooleanRenderer());
table.setDefaultRenderer(String.class, new StringCellRenderer(null));
table.setDefaultRenderer(Scope.class, new EnumCellRenderer());
table.setDefaultRenderer(SelectableSymbol.class, new SelectableSymbolRenderer());
table.setDefaultEditor(SelectableSymbol.class, SelectableSymbolEditor.createSelectableSymbolEditor());
table.setUpdateSelectionOnSort(true);
TableColumnModel tcm = table.getColumnModel();
for (int col = 0; col < tm.getColumnCount(); col++) {
int preferredWidth = tm.getColumnWidth(col);
if (preferredWidth > -1) {
tcm.getColumn(col).setPreferredWidth(preferredWidth);
}
}
table.setColumnSelectionAllowed(true);
table.setRowSelectionAllowed(true);
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
selectedObjectRow = table.getSelectedRow();
}
});
table.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() > 1) {
String objName = (String) tm.getValueAt(table.convertRowIndexToModel(selectedObjectRow), 0);
StationState ss = StationTracker.getInstance().getTrackedObject(objName);
if (ss != null) {
// just center and zoom to the target
YAAC.getGui().displayAttentionAlert(ss);
}
} else if (e.getButton() == MouseEvent.BUTTON3 ||
(e.getButton() == MouseEvent.BUTTON1 && (e.getModifiersEx() & InputEvent.ALT_DOWN_MASK) != 0)) {
Point p = e.getPoint();
int row = table.rowAtPoint(p);
int col = table.columnAtPoint(p);
if (row >= 0 && col >= 0 && !tm.isCellEditable(row, col)) {
row = table.convertRowIndexToModel(row);
StationState ss = tm.getSortedRow(row);
ArrayList<StationState> ssList = new ArrayList<StationState>(1);
ssList.add(ss);
JPopupMenu popup = MainGui.buildPopupMenu(p.x, p.y, null, ssList, GuiContentType.STATIONS);
// display the popup menu
if (popup != null) {
popup.show((Component) e.getSource(), e.getX(), e.getY());
}
}
}
}
});
table.addPropertyChangeListener("tableCellEditor", tm); // use this to stop async model updates when cell is being edited
TablePersister.getInstance().register(table, "StationList");
setViewportView(table);
setCorner(JScrollPane.UPPER_TRAILING_CORNER, tbShowMenu = smallScreen.createMenuDisplayToggleButton());
HelpAdapter.setHelpTag(this, "menu.View.SmallScreen.Map");
}
/**
* Change the state of this panel's menu toggle button to the specified state. If the
* state of the panel's toggle button already matches the specified state, do nothing
* (to avoid spurious event triggering).
*
* @param isShowing boolean true if button should be selected because menu is
* being displayed, false if not
*/
public void setMenuToggleButton(boolean isShowing) {
if (tbShowMenu.isSelected() != isShowing) {
tbShowMenu.setSelected(isShowing);
}
}
}

View File

@ -0,0 +1,204 @@
package org.ka2ddo.yaac.io;
/*
* Copyright (C) 2011-2018 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;
/**
* This class provides an adapter to convert an arbitrary DataInput implementer into a
* sequential input stream.
* @author Andrew Pavlin, KA2DDO
*/
public class ArbitraryDataInputStream extends InputStream {
private DataInput dataInput;
/**
* Create an new ArbitraryDataInputStream wrapper around the specified DataInput.
* Note that one could do this to a DataInputStream, but it would be redundant.
* @param dataInput dataInput implementor to wrap
*/
public ArbitraryDataInputStream(DataInput dataInput) {
this.dataInput = dataInput;
}
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
* <p> A subclass must provide an implementation of this method.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @throws java.io.IOException if an I/O error occurs.
*/
public int read() throws IOException {
return dataInput.readUnsignedByte();
}
/**
* Marks the current position in this input stream. A subsequent call to
* the <code>reset</code> method repositions this stream at the last marked
* position so that subsequent reads re-read the same bytes.
* <p> The <code>readlimit</code> arguments tells this input stream to
* allow that many bytes to be read before the mark position gets
* invalidated.
* </p>
* <p> The general contract of <code>mark</code> is that, if the method
* <code>markSupported</code> returns <code>true</code>, the stream somehow
* remembers all the bytes read after the call to <code>mark</code> and
* stands ready to supply those same bytes again if and whenever the method
* <code>reset</code> is called. However, the stream is not required to
* remember any data at all if more than <code>readlimit</code> bytes are
* read from the stream before <code>reset</code> is called.
* </p>
* <p> Marking a closed stream should not have any effect on the stream.
* </p>
* <p> The <code>mark</code> method of <code>InputStream</code> does
* nothing.
*
* @param readlimit the maximum limit of bytes that can be read before
* the mark position becomes invalid.
* @see java.io.InputStream#reset()
*/
@Override
public synchronized void mark(int readlimit) {
}
/**
* Tests if this input stream supports the <code>mark</code> and
* <code>reset</code> methods. Whether or not <code>mark</code> and
* <code>reset</code> are supported is an invariant property of a
* particular input stream instance. The <code>markSupported</code> method
* of <code>InputStream</code> returns <code>false</code>.
*
* @return <code>true</code> if this stream instance supports the mark
* and reset methods; <code>false</code> otherwise.
* @see java.io.InputStream#mark(int)
* @see java.io.InputStream#reset()
*/
@Override
public boolean markSupported() {
return false;
}
/**
* Reads some number of bytes from the input stream and stores them into
* the buffer array <code>b</code>. The number of bytes actually read is
* returned as an integer. This method blocks until input data is
* available, end of file is detected, or an exception is thrown.
* <p> If the length of <code>b</code> is zero, then no bytes are read and
* <code>0</code> is returned; otherwise, there is an attempt to read at
* least one byte. If no byte is available because the stream is at the
* end of the file, the value <code>-1</code> is returned; otherwise, at
* least one byte is read and stored into <code>b</code>.
* </p>
* <p> The first byte read is stored into element <code>b[0]</code>, the
* next one into <code>b[1]</code>, and so on. The number of bytes read is,
* at most, equal to the length of <code>b</code>. Let <i>k</i> be the
* number of bytes actually read; these bytes will be stored in elements
* <code>b[0]</code> through <code>b[</code><i>k</i><code>-1]</code>,
* leaving elements <code>b[</code><i>k</i><code>]</code> through
* <code>b[b.length-1]</code> unaffected.
* </p>
* <p> The <code>read(b)</code> method for class <code>InputStream</code>
* has the same effect as: <pre><code> read(b, 0, b.length) </code></pre>
*
* @param b the buffer into which the data is read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> is there is no more data because the end of
* the stream has been reached.
* @throws java.io.IOException If the first byte cannot be read for any reason
* other than the end of the file, if the input stream has been closed, or
* if some other I/O error occurs.
* @throws NullPointerException if <code>b</code> is <code>null</code>.
* @see java.io.InputStream#read(byte[], int, int)
*/
@Override
public int read(byte[] b) throws IOException {
dataInput.readFully(b);
return b.length;
}
/**
* Reads up to <code>len</code> bytes of data from the input stream into
* an array of bytes. An attempt is made to read as many as
* <code>len</code> bytes, but a smaller number may be read.
* The number of bytes actually read is returned as an integer.
* <p> This method blocks until input data is available, end of file is
* detected, or an exception is thrown.
* </p>
* <p> If <code>len</code> is zero, then no bytes are read and
* <code>0</code> is returned; otherwise, there is an attempt to read at
* least one byte. If no byte is available because the stream is at end of
* file, the value <code>-1</code> is returned; otherwise, at least one
* byte is read and stored into <code>b</code>.
* </p>
* <p> The first byte read is stored into element <code>b[off]</code>, the
* next one into <code>b[off+1]</code>, and so on. The number of bytes read
* is, at most, equal to <code>len</code>. Let <i>k</i> be the number of
* bytes actually read; these bytes will be stored in elements
* <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>,
* leaving elements <code>b[off+</code><i>k</i><code>]</code> through
* <code>b[off+len-1]</code> unaffected.
* </p>
* <p> In every case, elements <code>b[0]</code> through
* <code>b[off]</code> and elements <code>b[off+len]</code> through
* <code>b[b.length-1]</code> are unaffected.
* </p>
* <p> The <code>read(b,</code> <code>off,</code> <code>len)</code> method
* for class <code>InputStream</code> simply calls the method
* <code>read()</code> repeatedly. If the first such call results in an
* <code>IOException</code>, that exception is returned from the call to
* the <code>read(b,</code> <code>off,</code> <code>len)</code> method. If
* any subsequent call to <code>read()</code> results in a
* <code>IOException</code>, the exception is caught and treated as if it
* were end of file; the bytes read up to that point are stored into
* <code>b</code> and the number of bytes read before the exception
* occurred is returned. The default implementation of this method blocks
* until the requested amount of input data <code>len</code> has been read,
* end of file is detected, or an exception is thrown. Subclasses are encouraged
* to provide a more efficient implementation of this method.
*
* @param b the buffer into which the data is read.
* @param off the start offset in array <code>b</code>
* at which the data is written.
* @param len the maximum number of bytes to read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* @throws java.io.IOException If the first byte cannot be read for any reason
* other than end of file, or if the input stream has been closed, or if
* some other I/O error occurs.
* @throws NullPointerException If <code>b</code> is <code>null</code>.
* @throws IndexOutOfBoundsException If <code>off</code> is negative,
* <code>len</code> is negative, or <code>len</code> is greater than
* <code>b.length - off</code>
* @see java.io.InputStream#read()
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
dataInput.readFully(b, off, len);
return len;
}
}

View File

@ -0,0 +1,168 @@
package org.ka2ddo.yaac.repeaterfinder.gui;
/*
* Copyright (C) 2011-2021 Andrew Pavlin, KA2DDO
* This file is part of YAAC.
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.yaac.YAAC;
import org.ka2ddo.yaac.gps.GPSDistributor;
import org.ka2ddo.yaac.gui.FirstWindowInitIfc;
import org.ka2ddo.yaac.gui.FontCache;
import org.ka2ddo.yaac.gui.FontChangingMenuBar;
import org.ka2ddo.yaac.gui.MainGui;
import org.ka2ddo.yaac.gui.WindowPersister;
import org.ka2ddo.yaac.repeaterfinder.RepeaterFinderProvider;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.ResourceBundle;
/**
* This frame wraps the {@link RepeaterView} pane, so that the pane can also be used in
* the SmallScreen plugin.
* @author Andrew Pavlin, KA2DDO
*/
public class RepeaterFrame extends JFrame implements WindowListener, FirstWindowInitIfc {
private final RepeaterView repeaterView;
/**
* Create a RepeaterView based on the current GUI core.
*/
public RepeaterFrame() {
this((MainGui) YAAC.getGui());
}
/**
* Create a RepeaterView based on the specified GUI core.
*
* @param gui MainGui to use for service functions
*/
public RepeaterFrame(MainGui gui) {
final ResourceBundle msgBundle = YAAC.getMsgBundle(RepeaterView.BUNDLE_NAME);
String title = msgBundle.getString("title");
setTitle(title);
setIconImage(gui.getIconImage());
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setJMenuBar(new FontChangingMenuBar());
FontCache.setupJFrameForMenuFonts(this);
setContentPane(repeaterView = new RepeaterView());
addWindowListener(this);
}
/**
* Phase 2 of initializing the GUI (stuff that needs the GUI property of the main YAAC
* object to be already initialized).
*/
public void initMenuBar() {
MainGui.buildMenus(YAAC.getMenuBarActionList(), getJMenuBar(), this);
pack();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = getSize();
if (screenSize.getWidth() < frameSize.getWidth() ||
screenSize.getHeight() < frameSize.getHeight()) {
System.out.println("squeezed window size from " + frameSize + " to " + screenSize);
setUndecorated(true);
getGraphicsConfiguration().getDevice().setFullScreenWindow(this);
getRootPane().revalidate();
}
repeaterView.initMenuBar();
WindowPersister.getInstance().register(this, getTitle());
}
/**
* Invoked the first time a window is made visible.
*/
public void windowOpened(WindowEvent e) {
repeaterView.windowOpened(e);
}
/**
* Invoked when the user attempts to close the window
* from the window's system menu.
*/
public void windowClosing(WindowEvent e) {
// ignore
}
/**
* Invoked when a window has been closed as the result
* of calling dispose on the window.
*/
public void windowClosed(WindowEvent e) {
repeaterView.windowClosed(e);
}
/**
* Invoked when a window is changed from a normal to a
* minimized state. For many platforms, a minimized window
* is displayed as the icon specified in the window's
* iconImage property.
*
* @see java.awt.Frame#setIconImage
*/
public void windowIconified(WindowEvent e) {
// ignore
}
/**
* Invoked when a window is changed from a minimized
* to a normal state.
*/
public void windowDeiconified(WindowEvent e) {
// ignore
}
/**
* Invoked when the Window is set to be the active Window. Only a Frame or
* a Dialog can be the active Window. The native windowing system may
* denote the active Window or its children with special decorations, such
* as a highlighted title bar. The active Window is always either the
* focused Window, or the first Frame or Dialog that is an owner of the
* focused Window.
*/
public void windowActivated(WindowEvent e) {
// ignore
}
/**
* Invoked when a Window is no longer the active Window. Only a Frame or a
* Dialog can be the active Window. The native windowing system may denote
* the active Window or its children with special decorations, such as a
* highlighted title bar. The active Window is always either the focused
* Window, or the first Frame or Dialog that is an owner of the focused
* Window.
*/
public void windowDeactivated(WindowEvent e) {
// ignore
}
/**
* Releases all of the native screen resources used by this
* <code>Window</code>, its subcomponents, and all of its owned
* children. That is, the resources for these <code>Component</code>s
* will be destroyed, any memory they consume will be returned to the
* OS, and they will be marked as undisplayable.
*/
@Override
public void dispose() {
repeaterView.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,47 @@
package org.ka2ddo.yaac.core;
/*
* Copyright (C) 2011-2012 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
/**
* This exception is thrown to indicate that a ProgressDialog's cancel button has been pressed.
* @see org.ka2ddo.yaac.gui.ProgressDialog
* @see org.ka2ddo.yaac.core.StatusListener
*/
public class UserAbort extends RuntimeException {
/**
* Constructs a new runtime exception with <code>null</code> as its
* detail message. The cause is not initialized, and may subsequently be
* initialized by a call to {@link #initCause}.
*/
public UserAbort() {
super();
}
/**
* Constructs a new runtime exception with the specified detail message.
* The cause is not initialized, and may subsequently be initialized by a
* call to {@link #initCause}.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public UserAbort(String message) {
super(message);
}
}

View File

@ -0,0 +1,120 @@
package org.ka2ddo.yaac.io;
/*
* Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
/**
* InputStream to count the number of bytes read from the input, so as to drive
* a progress bar, but it avoids using the read() method of the passed-in
* InputStream, as SocketInputStream malloc's a 1-byte byte array for each such read,
* then throws the array away.
* @author Andrew Pavlin, KA2DDO
*/
final public class CountingInputStreamForSockets extends java.io.FilterInputStream {
/**
* The byte count.
*/
private long byteCount = 0L;
/**
* Temporary buffer array.
*/
private byte[] buf = new byte[1];
/**
* Create a CountingInputStream wrapped around the specified InputStream.
*
* @param in the InputStream to count.
*/
public CountingInputStreamForSockets(java.io.InputStream in) {
super(in);
}
/**
* Override the markSupported operation.
*/
public boolean markSupported() {
// Keep from marking so the byte count isn't corrupted.
return false;
}
/**
* Override mark to do nothing.
*/
public void mark(int readlimit) {
}
/**
* Override reset to reset the byte count.
*/
public void reset() {
byteCount = 0;
}
/**
* Read a byte.
*/
public final int read() throws java.io.IOException {
int answer;
if (in.read(buf, 0, 1) > 0) {
byteCount++;
return buf[0] & 0xFF;
}
return -1;
}
/**
* Read an array of bytes.
*/
public final int read(byte[] b) throws java.io.IOException {
int answer;
if ((answer = in.read(b, 0, b.length)) > 0) {
byteCount += answer;
}
return answer;
}
/**
* Read bytes into an offset array.
*/
public final int read(byte[] b, int off, int len) throws java.io.IOException {
int answer;
if ((answer = in.read(b, off, len)) > 0) {
byteCount += answer;
}
return answer;
}
/**
* Skip some bytes.
*/
public final long skip(long n) throws java.io.IOException {
long answer;
if ((answer = in.skip(n)) > 0) {
byteCount += answer;
}
return answer;
}
/**
* Get the number of bytes passed through this stream (so far).
* @return the number of read bytes so far.
*/
public final long getByteCount() {
return byteCount;
}
}

View File

@ -0,0 +1,276 @@
package org.ka2ddo.yaac.io;
/*
* Copyright (C) 2011-2021 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.ax25.Connector;
import org.ka2ddo.util.DebugCtl;
import java.lang.reflect.Method;
import java.net.UnknownServiceException;
import java.util.*;
import java.io.IOException;
/**
* This class defines the generic API for an object that connects a bidirectional
* real-time data stream to this application.
*
* <p>To support the dynamically plugin-built support for multiple port types, each
* non-abstract PortConnector subclass must define the following public static final
* fields:
* <ul>
* <li>TYPE_NAME - a String specifying the internal type name of the class (stored in the
* {@link PortConfig#portType} field of PortConfig records)
* <li>CONFIG_GUI - a wildcarded fully-qualified String name of a Class which implements an
* editor for the configuration parameters for the PortConnector
* subclass
* </ul>
* <p>To support configuration transfer, the subclass must also define the following
* public static final fields:</p>
* <ul>
* <li>BLANK_FIELDS - a Set of the {@link PortConfig.Fields} enums for the fields that should not be copied for this port driver</li>
* <li>REQ_FIELDS - a Map of the {@link PortConfig.Fields} enums for the fields that must be defined for this port driver,
* mapping to {@link PortConfig.RequireHints} structures containing the ResourceBundle keys for their UI prompt strings
* if they are undefined</li>
* </ul>
* <p>If a field is both required and blanked, the configuration import code will prompt for the missing field.</p>
*
* <p>To support the -createport command-line option, a port type whose port editor class changes the
* defaults in the PortConfig.Cfg object for a new port should have those changes performed by a
* public static method named fillinConfigDefaults(PortConfig.Cfg) in the PortConnector subclass,
* so that the command-line port creation logic can fill in those defaults to deal with new port
* specifications that are not complete.</p>
*
* @author Andrew Pavlin, KA2DDO
* @see org.ka2ddo.yaac.io.PortConfig.RequireHints
*/
abstract public class PortConnector extends Connector {
/**
* Timer for use by PortConnectors to handle inactivity tests.
*/
private static Timer portConnectorInactivityTimer = null;
/**
* The port configuration associated with this PortConnector.
*/
public PortConfig portConfig;
/**
* The port type-specific configuration associated with this PortConnector.
*/
public PortConfig.Cfg currentCfg;
private final ArrayList<PortEventListener> eventListeners = new ArrayList<PortEventListener>();
private static final HashMap<String, Class<? extends PortConnector>> PORT_CONNECTOR_MAP =
new LinkedHashMap<String, Class<? extends PortConnector>>();
/**
* Get the Timer for use by PortConnectors to handle inactivity tests.
* @return Timer dedicated to PortConnector timeout checks
*/
protected static Timer getPortConnectorInactivityTimer() {
if (portConnectorInactivityTimer == null) {
if (DebugCtl.isDebug("thread")) {
new Throwable("creating PortConnectorInactivityTimer...").printStackTrace(System.out);
}
portConnectorInactivityTimer = new Timer("PortConnector Inactivity Timer", true);
}
return portConnectorInactivityTimer;
}
/**
* Add a new subclass of PortConnector to the supported map of PortConnector types.
* Note that putting a new Class object with the same typeName will replace any
* existing driver for that type name.
* @param typeName String name of PortConnector subclass
* @param pcClazz the Class object for instantiating objects of this type
*/
public static void registerPortConnectorType(String typeName, Class<? extends PortConnector> pcClazz) {
PORT_CONNECTOR_MAP.put(typeName, pcClazz);
}
/**
* Get all the registered PortConnector names that should be displayed in the port configuration
* dialog as a port type.
* @return Set of String names to be displayed as port driver types.
*/
public static Set<String> portTypeNames() {
return PORT_CONNECTOR_MAP.keySet();
}
/**
* Get all the concrete subclasses of PortConnector currently registered in YAAC for
* which instances can be created and configured.
* @return Collection of subclasses of PortConnector
*/
public static Collection<Class<? extends PortConnector>> portConnectorClasses() {
return PORT_CONNECTOR_MAP.values();
}
/**
* Get the class corresponding to a particular port type name.
* @param portTypeName String name of port connector type
* @return Class providing that port connector type, or null if no match
*/
public static Class<? extends PortConnector> getPortConnectorClass(String portTypeName) {
return PORT_CONNECTOR_MAP.get(portTypeName);
}
/**
* Register a listener for port transmission and/or reception events.
* @param l PortEventListener to register
*/
public void addPortEventListener(PortEventListener l) {
synchronized (eventListeners) {
for (int i = 0; i < eventListeners.size(); i++) {
if (eventListeners.get(i) == l) {
return;
}
}
eventListeners.add(l);
}
}
/**
* Unregister a listener so it no longer is informed of transmission and
* reception events.
* @param l PortEventListener to unregister
*/
public void removePortEventListener(PortEventListener l) {
synchronized (eventListeners) {
for (int i = 0; i < eventListeners.size(); i++) {
if (eventListeners.get(i) == l) {
eventListeners.remove(i);
break;
}
}
}
}
/**
* Report to interested listeners that the port has started or stopped transmitting.
* @param isTransmitting boolean true if transmission has started, false if stopped
*/
protected void fireTransmitting(boolean isTransmitting) {
ArrayList<PortEventListener> eventListeners = this.eventListeners;
for (int i = 0; i < eventListeners.size(); i++) {
eventListeners.get(i).portTransmitting(this, isTransmitting);
}
}
/**
* Report to interested listeners that the port has started or stopped receiving.
* @param isReceiving boolean true if reception has started, false if stopped
*/
public void fireReceiving(boolean isReceiving) {
ArrayList<PortEventListener> eventListeners = this.eventListeners;
for (int i = 0; i < eventListeners.size(); i++) {
eventListeners.get(i).portReceiving(this, isReceiving);
}
}
/**
* Report to interested listeners that the port has failed for some reason.
*/
public void fireFailed() {
System.out.println(new Date().toString() + ": port " + toString() + " failed");
ArrayList<PortEventListener> eventListeners = this.eventListeners;
for (int i = 0; i < eventListeners.size(); i++) {
eventListeners.get(i).portFailed(this);
}
}
/**
* Fill in the port-type-specific defaults for a port configuration.
* @param cfg PortConfig whose default values are to be adjusted
* @throws UnknownServiceException if the port type is unknown
* @throws IOException if some other problem occurs trying to fill in the defaults
*/
public static void fillinConfigDefaultsForType(PortConfig cfg) throws UnknownServiceException, IOException {
Class<? extends PortConnector> connectorClass = PORT_CONNECTOR_MAP.get(cfg.portType);
if (connectorClass == null) {
throw new UnknownServiceException("no driver for port type " + cfg.portType);
}
try {
Method mInitCfg = connectorClass.getDeclaredMethod("fillinConfigDefaults", PortConfig.Cfg.class);
mInitCfg.invoke(null, cfg.current());
} catch (NoSuchMethodException e) {
// ignore, this port driver doesn't need anything different than the PortConfig.Cfg's constructor
} catch (Exception e) {
throw new IOException("unable to instantiate PortConnector for " + cfg, e);
}
}
/**
* Instantiate a PortConnector object of the type specified in the configuration object.
* @param cfg PortConfig object identifying what type of port to create
* @return instantiated but not configured PortConnector subclass object
* @throws IOException if port could not be instantiated
* @throws UnknownServiceException if port type not recognized
*/
public static PortConnector createPort(PortConfig cfg) throws UnknownServiceException, IOException {
Class<? extends PortConnector> connectorClass = PORT_CONNECTOR_MAP.get(cfg.portType);
if (connectorClass == null) {
throw new UnknownServiceException("no driver for port type " + cfg.portType);
}
try {
return connectorClass.newInstance();
} catch (Exception e) {
throw new IOException("unable to instantiate PortConnector for " + cfg, e);
}
}
/**
* Update the configuration of the connector to match the updated
* setup.
* @param cfg PortConfig defining new port settings
* @throws java.io.IOException if interface changes could not be applied
* @throws IllegalArgumentException if type information is invalid for
* changing the settings of this PortConnector
*/
abstract public void configure(PortConfig cfg) throws IOException, IllegalArgumentException;
/**
* Get the current configuration object for this PortConnector. Note that the caller should not modify
* this data structure; if the port parameters are to be changed, the PortConnector should be closed
* and then configured with an updated PortConfig object.
* @return PortConfig for this PortConnector (may return null for a non-configured port)
* @see #configure(PortConfig)
* @see #close()
*/
public PortConfig getPortConfig() {
return portConfig;
}
/**
* Store the configuration associated with this PortConnector. The PortConfig is assumed to
* be of the correct type here, such that the current() method will return the appropriate
* type-specific configuration data.
* @param portConfig PortConfig matching the PortConnector subclass's type
*/
public void setPortConfig(PortConfig portConfig) {
this.portConfig = portConfig;
this.currentCfg = portConfig.current();
}
/**
* Get the bit transmission rate on this port. This abstract superclass does not know what the
* data rate is, and expects subclasses to override this method.
* @return bit rate in bits per second (baud)
*/
public int getBitRate() {
return 0;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

View File

@ -0,0 +1,383 @@
###############################################################################
# Copyright (C) 2011-2021 Andrew Pavlin, KA2DDO
# This file is part of YAAC (Yet Another APRS Client).
#
# YAAC is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YAAC 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
# and GNU Lesser General Public License along with YAAC. If not,
# see <http://www.gnu.org/licenses/>.
###############################################################################
#
# this file contains the hexadecimal encoding of variable-length OpenTRAC symbol IDs, translating to
# a text string description followed by a comma and either:
# a, a 2-character APRS symbol code (for an APRS icon equivalent to the OpenTrac icon)
# (backslash has to be escaped per programming standards),
# b. a relative path name within the YAAC distro for an image file for the icon (not matching
# any existing APRS symbol code),
# c. nothing (no comma), for a symbol code without a corresponding icon.
#
10=Space,
11=Manned Space,
1110=Shuttle,/S
1120=Soyuz,\\O
1130=Station,\\S
12=Unmanned Space
1210=Satellite,\\S
1220=Cargo Ship,\\O
1230=Debris,\\x
20=Air
21=Lighter than Air
2110=Manned Lighter than Air,/O
2111=Hot Air Balloon,/O
2112=Helium,/O
2120=Unmanned Lighter than Air,/O
2121=Radiosonde,/O
2122=Experimental Balloon,/O
22=Fixed Wing Air
2210=Private Fixed Wing Air
2211=Experimental,X^
2212=Sailplane,/g
2213=Piston Airplane,/'
221310=Piston Airplane (1-engine),/'
221320=Piston Airplane (multi-engine),/^
2214=Turboprop Airplane,P^
221410=Turboprop Airplane (1-engine),P^
221420=Turboprop Airplane (multi-engine),P^
2215=Jet Airplane
221510=Jet Airplane (1-engine),/'
221520=Jet Airplane (multi-engine),J^
2220=Commercial Airplane,/^
2221=Cargo Airplane,/^
2222=Medevac Airplane,/'
2223=Photo Airplane,/'
2224=Utility Airplane,/'
2225=SAR Airplane,\\^
2226=UAV/Drone/RC Airplane,D^
2230=Military,/^
23=Rotary Wing Aircraft,/X
2310=Private Rotary Wing,/X
2311=Experimental Private Rotary Wing,/X
2320=Commercial Rotary Wing,/X
2321=Cargo Helicopter,/X
2322=Medevac Helicopter,/X
2323=Photo Helicopter,/X
2324=Utility Helicopter,/X
2325=UAV/Drone/RC Helicopter,D^
24=Ultralight
2410=Powered Ultralight,/'
2420=Unpowered Ultralight,/'
2421=Paraglider,/g
2422=Hang Glider,/g
30=Ground
31=Ground Facilities
3120=Government Facility,Gz
3130=Medical Facility,/h
3131=Hospital,/h
3132=Clinic,Cz
3133=Triage Facility,Tz
3134=First Aid Station,/A
3135=Pharmacy,\\X
3136=Morgue,Mz
3140=Emergency Services,\\c
3141=Fire Station,/d
3142=Decontamination,Dc
3143=Shelter,\\z
3144=EOC,/o
3145=Incident Command Post,/c
3146=Red Cross,/+
3147=RACES/Civil Defense,\\c
3150=Utilities
3151=Power,\\%
3152=Water,/w
3160=Ham Radio
3161=Repeater Site,/r
3162=Ham Store,\\h
3163=Club Station,C-
3170=Residential
3171=House,/-
3172=Apartment
3173=Condo
3180=Church,\\+
3190=Services
3191=Hotel,/H
3192=Gas,\\9
3193=Restaurant,\\R
3194=Information,\\?
3195=Parking,\\P
3196=Truck Stop,/t
3197=Restrooms,\\r
3198=Bank/ATM,\\$
31A0=School,/K
31A1=Elementary School,/K
31A2=Middle School,/K
31A3=Secondary School,/K
31A4=College,/K
31B0=Law Enforcement,/!
31B1=Police Station,/!
31B2=Sheriff Station,/!
31B3=Ranger Station,/!
31C0=Community Org
31C1=Boy Scouts,/,
31C2=Girl Scouts,\\,
31D0=Recreation
31D1=Park,\\;
31D2=Picnic Area,\\;
31D3=Campground,/;
31E0=Transportation
31E1=Air Transport
31E110=Airport,AD
31E120=Ultralight Flight Area
31E130=VORTAC Nav Aid,\\V
31E140=Helipad,HD
31E2=Ground Transport
31E210=Train Station,RD
31E220=Bus Station,BD
31E230=Light Rail,LD
31E3=Sea Transport,SD
31E310=Port,SD
31E320=Ferry Landing,FD
31E330=Lighthouse,\\L
31F0=Communications
31F1=Telephone,/$
31F110=Switching Center,/$
31F120=Cell Site,/r
31F2=Satellite Ground Station,/`
32=Equipment
3210=Vehicle,\\>
3211=Passenger Vehicle,/>
321110=Car,/>
321120=Van,/v
321130=Truck,\\u
321140=SUV,\\k
321150=RV,/R
321151=Class A RV,/R
321152=Class B RV,/R
321153=Class C RV,/R
321160=Motorcycle,/<
321170=Bicycle,/b
3212=Commercial Vehicle,/u
321210=Tractor/Trailer,/u
321220=Bus,/U
3213=Rail Vehicle,/=
321310=Locomotive,/=
3214=Snow Vehicle
321410=Snowmobile,/*
321420=Snow Plow,Pu
3215=Construction Vehicle
321510=Bulldozer,Bu
321520=Backhoe,Bu
321530=Loader
321540=Crane,\\j
3216=Public Safety Vehicle,/P
321610=Law Enforcement,/P
321620=Ambulance,/a
321630=Fire Truck,/f
321631=Brush Fire Truck,/f
321632=Aerial Ladder Truck,/f
3220=Emergency Equipment
3221=Generator,\\%
322110=Portable Generator,P%
322120=Towed Generator,P%
322130=Fixed Generator,E%
33=Communications Equipment
3310=Mobile Satellite Station,/(
3430=People,/[
40=Sea
41=Sea Vessel,\\s
4110=Naval Vessel,Ms
4120=Merchant Vessel,\\s
4121=Bulk Carrier Vessel,Cs
4122=Container Vessel,Cs
4123=Roll-on/off Vessel,Cs
4124=Tanker Vessel,Cs
412410=Coastal Tanker,Cs
412420=LCC Tanker,Cs
412430=VLCC Tanker,Cs
412440=ULCC Tanker,Cs
412450=Barge Tanker,Cs
4130=Support Vessel
4131=Tugboat,Ts
413110=Harbor Tug,Ts
413120=Ocean Tug,Ts
4132=Pilot Boat,Ps
4133=Dredge,\\s
4134=Barge,\\s
4135=Ferry,Xs
413510=Passenger Ferry,Xs
413520=Vehicle/Train Ferry,Xs
413530=Hovercraft,Ws
413540=Hydrofoil,Ws
4137=Towing Vessel,\\s
4140=Fishing Vessel,Fs
4141=Long Liner,Fs
4142=Seiner,Fs
4143=Drifter,Fs
4144=Dredge Fisher,Fs
4145=Trawler,Fs
4150=Leisure Craft,Bs
4151=Canoe,/C
4152=Kayak,/C
4153=Rowboat,/s
4154=Yacht,/Y
415410=Sail Yacht,/Y
415420=Power Yacht,/Y
415430=SportFishing Yacht,/Y
4155=PWC,Js
4156=Skiff,/s
4157=Launch,/s
4158=DaySail,/Y
4159=Cruise Ship,\\s
415A=Tall Ship,Ys
4160=Law Enforcement Vessel,Ls
4161=Harbor Patrol,Ls
4162=Coast Guard,\\C
42=Sea Structure
4210=Floating Structure
4211=Oil Rig,Os
4212=Dry Dock
4213=Bridge
4220=Fixed Structure
4221=Oil Rig,Os
4222=Dry Dock
4223=Anchor Area
4224=Mooring
4225=Mooring Area
4226=Bridge
422610=Fixed Bridge
422620=Lift/Swing Bridge
43=Buoy,\\N
4310=Navigation Buoy,\\N
4320=Weather Buoy,\\N
50=Subsurface
51=Subsurface Equipment
5110=Subsurface Vehicle,Us
5111=ROV,Us
5112=Submersible,Us
52=features
5210=Dive Site
5220=Channel
60=Activities
61=Ham Radio
6110=Ham Radio Contest
6111=Field Day,F;
6120=Swap Meet,/E
6130=Convention,/E
6140=DX Cluster,/%
62=Sports
6210=Geocache
6220=Bike Race,/b
6230=Foot Race,R[
63=Public Events
6310=Parade,/[
6311=Parade Staging Area,S0
6312=Parade Review Stand,/E
6420=Fire,/:
6421=Wildland Fire,/:
642110=Small Wildfire,/:
622120=Medium Wildfire,/:
622130=Large Wildfire,/:
6422=Structure Fire,/:
65=SAR
6510=SAR Victim
6520=SAR Tracks
6530=SAR Sign
6540=SAR Evidence
6550=SAR Crash Site,\\'
6560=Shipwreck,6s
6570=SAR Man Overboard,\\C
6580=ELT/EPIRB Hit,E!
66=Hazard
6610=Earthquake,\\Q
6620=HAZMAT,WH
6630=Flooding,\\w
6640=Landslide/Mudslide,Mw
6650=Avalanche,Aw
6660=Volcanic Eruption,V!
70=Weather
71=Atmospheric Weather
7110=Weather Pressure Systems
7111=Low Pressure Center
7112=High Pressure Center
7113=Frontal Systems
711310=Cold Front
711311=Upper Cold Front
711320=Warm Front
711321=Upper Warm Front
711330=Occluded Front
711340=Stationary Front
7114=Weather Lines
711410=Trough Line
711420=Ridge Line
711430=Squall Line
7120=Turbulence
7121=Light Turbulence
7122=Medium Turbulence
7123=Severe Turbulence
7124=Extreme Turbulence
7130=Icing
7131=Clear Icing
713110=Light Clear Icing
713120=Moderate Clear Icing
713130=Severe Clear Icing
7132=Rime Icing
713210=Light Rime Icing
713220=Moderate Rime Icing
713230=Severe Rime Icing
7133=Mixed Icing
713310=Light Mixed Icing
713320=Moderate Mixed Icing
713330=Severe Mixed Icing
7140=Wind Barb
7141=Jet Stream
7150=Flight Rules
7151=Instrument Ceiling
7152=Visual Ceiling
7160=Coverage Symbols
7161=Clear Sky,\\U
7162=Scattered,\\p
7163=Broken Clouds,\\p
7164=Overcast with Breaks,\\(
7165=Overcast,\\(
7166=Sky Obscured,\\(
7170=Precipitation
7171=Rain,\\`
717110=Rain Shower,\\I
717120=Freezing Rain,\\F
717130=Drizzle,\\D
717131=Freezing Drizzle,\\F
7172=Snow,\\*
717210=Snow Showers,\\G
717220=Snow Grains,\\*
7173=Hail,\\:
7174=Ice Pellets
7175=Ice Crystals
7180=Storms
7181=Thunderstorm,\\T
718110=Thunderstorm with Rain,\\T
718120=Funnel Cloud,\\f
718130=Lightning,\\J
7182=Storm Systems
718210=Tropical Storm,\\@
718220=Hurricane,\\@
7190=Visibility Obstructions
7191=Blowing Snow,\\B
7192=Fog,\\{
719210=Freezing Fog,\\{
7193=Dust/Sand Storm,DE
7194=Dust Devil
7195=Smoke,\\E
7196=Haze,\\H
7197=Blowing Sand/Dust,DE
72=Oceanic
73=Space

View File

@ -0,0 +1,124 @@
package org.ka2ddo.yaac.core.queries;
/*
* Copyright (C) 2011-2021 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.aprs.Message;
import org.ka2ddo.aprs.MessageMessage;
import org.ka2ddo.aprs.NWSMultiLine;
import org.ka2ddo.aprs.ObjectReport;
import org.ka2ddo.ax25.AX25Callsign;
import org.ka2ddo.ax25.AX25Frame;
import org.ka2ddo.ax25.Connector;
import org.ka2ddo.util.DistanceUnit;
import org.ka2ddo.yaac.YAAC;
import org.ka2ddo.ax25.AX25Stack;
import org.ka2ddo.yaac.core.AlohaRanger;
import org.ka2ddo.yaac.core.AlohaTracker;
import org.ka2ddo.yaac.core.YAACPreferences;
import org.ka2ddo.yaac.io.BeaconData;
import org.ka2ddo.yaac.io.PortConnector;
import org.ka2ddo.yaac.io.Transmitter;
import org.ka2ddo.yaac.pluginapi.AbstractQueryHandler;
/**
* This class handles queries for the local station's RF Aloha circle radius and position.
* @author Andrew Pavlin, KA2DDO
*/
public class AlohaQueryHandler extends AbstractQueryHandler {
/**
* Handle the passed in message and generate whatever appropriate response should be made.
*
* @param mm MessageMessage addressed explicitly to local station whose content begins with
* one of the prefixes specified by the subclass.
*/
public void handleQuery(Message mm) {
// send description of YAAC's local aloha circle on the port receiving this message
AX25Frame f = mm.getAx25Frame();
if (f == null) {
return; // didn't come from a real interface
}
AlohaRanger[] alohaRangers = AlohaTracker.getInstance().getAlohaRanges();
if (alohaRangers.length > 0) {
// we'll try to find the one corresponding to the interface the request came in on
AlohaRanger target = null;
for (AlohaRanger ar : alohaRangers) {
if (ar.port == f.sourcePort) {
target = ar;
break;
}
}
if (target == null) {
target = alohaRangers[0];
}
double centerLat = AlohaTracker.getInstance().getLastLatitude();
double centerLon = AlohaTracker.getInstance().getLastLongitude();
// draw a 16-sided polygon as an approximation of a circle
double[] lats = new double[16];
double[] lons = new double[16];
double lonScale = 1.0 / Math.cos(Math.toRadians(centerLat));
double rangeInDeg = (double) DistanceUnit.CONV_METERS_TO_DEGLAT * target.getRangeM();
int index = 0;
for (double angle = 0.0; angle < 2.0 * Math.PI; angle += 0.125 * Math.PI, index++) {
lats[index] = centerLat + Math.sin(angle) * rangeInDeg;
lons[index] = centerLon + Math.cos(angle) * rangeInDeg * lonScale;
}
NWSMultiLine nws = new NWSMultiLine(lats, lons, true);
nws.dataType = (target.getNumMsgs() < AlohaTracker.MAX_ALOHA_1200_CAPACITY) ? 'n' : 'm';
// get the appropriate BeaconData for the receiving port
BeaconData beaconData = null;
if (mm.ax25Frame != null) {
Connector connector = mm.ax25Frame.sourcePort;
if (connector != null && connector.hasCapability(Connector.CAP_XMT_PACKET_DATA)) {
PortConnector portConnector = (PortConnector)connector;
if (portConnector.currentCfg.transmitAllowed && portConnector.currentCfg.beaconNames.length > 0) {
for (String beaconName : portConnector.currentCfg.beaconNames) {
beaconData = YAAC.getBeaconData(beaconName);
if (beaconData != null) {
if (beaconData.enabled) {
break;
}
beaconData = null; // try another one that might be enabled
}
}
}
}
}
if (beaconData == null) {
beaconData = YAAC.getBeaconData(); // get the default
}
// transmit the resulting message as an Item-in-Msg
DistanceUnit prefDistanceUnit = YAAC.getDistanceUnit();
ObjectReport or = nws.generateMessage(target.port.getCallsign(), beaconData.symTableId, beaconData.symbolCode,
" Aloha" + (target.isMaxedOut() ? "" : "?") + " " + Math.round(target.getRangeM() * prefDistanceUnit.from(DistanceUnit.METER)) +
prefDistanceUnit.name() + " to " + target.getMostDistantStation().getIdentifier());
MessageMessage replyMM = new MessageMessage(mm.originatingCallsign, or.getStringAPRSBody(false));
AX25Frame replyFrame = new AX25Frame();
replyFrame.dest = new AX25Callsign(YAAC.getPreferences().get(YAACPreferences.PREF_BEACON_DESTINATION, YAACPreferences.DEFAULT_PREF_BEACON_DESTINATION));
replyFrame.sender = new AX25Callsign(f.sourcePort.getCallsign());
replyFrame.digipeaters = AX25Stack.reverseDigipeaters(f.digipeaters);
replyFrame.ctl = (AX25Frame.FRAMETYPE_U | AX25Frame.UTYPE_UI);
replyFrame.pid = AX25Frame.PID_NOLVL3;
replyFrame.body = replyMM.getBody(false, AX25Frame.PID_NOLVL3, replyFrame);
Transmitter.getInstance().queue(replyFrame);
}
}
}

View File

@ -0,0 +1,43 @@
package org.ka2ddo.yaac.aisdecoder.messages;
/*
* Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
* This file is part of YAAC.
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
/**
* This enum defines the values of the NavigationStatus field of AIS Position Reports.
* @author Andrew Pavlin, KA2DDO
*/
public enum NavigationStatus {
UNDER_WAY_ENGINE,
AT_ANCHOR,
NOT_UNDER_COMMAND,
RESTRICTED_MANOEUVERABILIY,
DRAUGHT_CONSTRAINED,
MOORED,
AGROUND,
FISHING,
UNDER_WAY_SAIL,
reserved_HSC,
reserved_WIG,
reserved_11,
reserved_12,
reserved_13,
AIS_SART,
UNDEFINED
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE map
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 2.0//EN"
"http://java.sun.com/products/javahelp/map_2_0.dtd">
<!--
Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
This file is part of YAAC (Yet Another APRS Client).
YAAC is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
YAAC 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
and GNU Lesser General Public License along with YAAC. If not,
see <http://www.gnu.org/licenses/>.
-->
<map version="2.0">
<mapID target="configure" url="docs/configure.html"/>
<mapID target="configure.ports" url="docs/config_ports.html"/>
<mapID target="configure.AdsB" url="docs/config_adsb.html"/>
<mapID target="menu.View.Aircraft" url="docs/viewaircraft.html"/>
</map>

View File

@ -0,0 +1,38 @@
package org.ka2ddo.yaac.core;
/*
* Copyright (C) 2011-2018 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
/**
* This interface provides a callback so something can be asynchronously notified that
* the user has requested cancellation of a long-running background job. Otherwise,
* the long-running job is only notified of the cancellation the next time updates
* are sent to a StatusListener.
* @see StatusListener
* @see UserAbort
* @author Andrew Pavlin, KA2DDO
*/
public interface AbortListener {
/**
* Called from the user interface when the user requests a cancel. Implementations of this
* method should not block, but should quickly inform the associated long-running background
* thread to abort its operation.
*/
public void cancelRequested();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1 @@
YAAC-Provider: org.ka2ddo.yaac.sounds.SoundsProvider

View File

@ -0,0 +1,278 @@
package org.ka2ddo.yaac.gui;
/*
* Copyright (C) 2011-2021 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.aprs.Message;
import org.ka2ddo.aprs.Symbols;
import org.ka2ddo.yaac.YAAC;
import org.ka2ddo.yaac.io.NonshareableBufferedInputStream;
import org.ka2ddo.yaac.util.Json;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TreeSet;
/**
* This class collects the localized mappings of to-call patterns to sender descriptions.
*/
public class TocallToDescriptionMap {
private static final TocallToDescriptionMap instance = new TocallToDescriptionMap();
private static final String FMT_GPS = YAAC.getMsg("tocallFmt.GPS");
private static final String FMT_SPC = YAAC.getMsg("tocallFmt.SPC");
private final HashMap<String, TocallDescription> tocallToDescriptionMap = new HashMap<String, TocallDescription>();
/**
* Get the singleton object for translating tocalls into descriptions.
* @return TocallToDescriptionMap singleton object
*/
public static TocallToDescriptionMap getInstance() {
return instance;
}
/**
* Data structure containing the description and capabilities of an APRS station with a specific tocall.
*/
public static class TocallDescription {
private static final String fmtVendorModel = YAAC.getMsg("tocallFmt.VendorModel");
private static final String fmtVendorModelOs = YAAC.getMsg("tocallFmt.VendorModelOs");
private static final String fmtVendorOs = YAAC.getMsg("tocallFmt.VendorOs");
private static final String fmtModelOs = YAAC.getMsg("tocallFmt.ModelOs");
private static final String fmtUnknown = YAAC.getMsg("tocallFmt.Unknown");
final String tocall;
String model;
String vendor;
String clazz;
String os;
HashMap<String,String> features;
TocallDescription(String tocall) {
this.tocall = tocall;
}
/**
* Get the class of station for this tocall (not associated with symbol categories).
* @return station class String, or null if class is not known
*/
public String getClazz() {
return clazz;
}
/**
* Get the collection of feature strings and their associated values for this tocall.
* @return the feature Map
*/
public Map<String, String> getFeatures() {
if (null != features) {
return Collections.unmodifiableMap(features);
} else {
return Collections.emptyMap();
}
}
/**
* Get the model name of the software or hardware associated with this tocall.
* @return model name
*/
public String getModel() {
return model;
}
/**
* Get the computer operating system this software is restricted to.
* @return operating system name String, or null if not known or not applicable
*/
public String getOs() {
return os;
}
/**
* Get the tocall described by this structure.
* @return tocall String
*/
public String getTocall() {
return tocall;
}
/**
* Get the name of the vendor who manufactured this APRS hardware or software.
* @return vendor name, or null if not known
*/
public String getVendor() {
return vendor;
}
/**
* Returns a string representation of the object. The representation is the vendor's name (in the
* possessive syntax) of the model string for the specified operating system (if relevant).
*
* @return a string representation of the object.
*/
@Override
public String toString() {
if (model != null) {
if (vendor != null && !vendor.equals("unknown")) {
if (os != null) {
return MessageFormat.format(fmtVendorModelOs, vendor, model, os);
} else {
return MessageFormat.format(fmtVendorModel, vendor, model);
}
} else {
if (os != null) {
return MessageFormat.format(fmtModelOs, model, os);
} else {
return model;
}
}
} else {
if (vendor != null) {
if (os != null) {
return MessageFormat.format(fmtVendorOs, vendor, os);
} else {
return vendor;
}
} else {
return fmtUnknown;
}
}
}
}
private TocallToDescriptionMap() {
// read the JSON map
Reader rdr = null;
try {
rdr = new InputStreamReader(new NonshareableBufferedInputStream(new FileInputStream(new File(YAAC.getBaseDir(), "images/tocalls.dense.json")), 4096), StandardCharsets.UTF_8);
Map<String,Object> tocallJsonMap = Json.decodeJSON(rdr, null);
rdr.close();
rdr = null;
Map<String,Object> tocallSubMap = (Map<String,Object>)tocallJsonMap.get("tocalls");
for (Map.Entry<String,Object> entry : tocallSubMap.entrySet()) {
String key = entry.getKey();
while (key.length() > 0 && key.endsWith("?")) {
key = key.substring(0, key.length() - 1);
}
if (key.length() > 0) {
TocallDescription descr = new TocallDescription(entry.getKey());
Map<String,Object> infoMap = (Map<String,Object>)entry.getValue();
descr.model = (String)infoMap.get("model");
descr.vendor = (String)infoMap.get("vendor");
if ("open source".equalsIgnoreCase(descr.vendor)) {
descr.vendor = null;
}
if ("unknown".equalsIgnoreCase(descr.model) && null != descr.vendor) {
descr.model = null;
}
descr.clazz = (String)infoMap.get("class");
descr.os = (String)infoMap.get("os");
Object features = infoMap.get("features");
if (features instanceof Object[]) {
descr.features = new LinkedHashMap<String, String>();
for (Object o : (Object[])features) {
descr.features.put((String)o, "");
}
} else if (features instanceof Map) {
descr.features = new LinkedHashMap<String, String>((Map)features);
}
int midrangeWildcardPos;
if ((midrangeWildcardPos = key.indexOf('?')) == -1) {
tocallToDescriptionMap.put(key, descr);
} else {
//TODO: how to handle matching when version not at end
}
}
}
} catch (IOException e) {
e.printStackTrace(System.out);
} finally {
if (null != rdr) {
try {
rdr.close();
} catch (IOException e) {
e.printStackTrace(System.out);
}
}
}
// now read in the resource bundle's definitions to fill in any holes
ResourceBundle msgBundle = YAAC.getMsgBundle();
TreeSet<String> redundantTocalls = new TreeSet<String>();
TreeSet<String> missingTocalls = new TreeSet<String>();
for (String key : msgBundle.keySet()) {
if (key.startsWith("tocall.")) {
String tocall = key.substring(7);
if (!tocallToDescriptionMap.containsKey(tocall)) {
String model = msgBundle.getString(key);
TocallDescription descr = new TocallDescription(tocall);
descr.model = model;
tocallToDescriptionMap.put(tocall, descr);
missingTocalls.add(tocall);
} else {
redundantTocalls.add(tocall);
}
}
}
if (redundantTocalls.size() > 0) {
System.out.println("redundant tocalls from ResourceBundle: " + redundantTocalls);
}
if (missingTocalls.size() > 0) {
System.out.println("missing tocalls in JSON file, using fallback: " + missingTocalls);
}
}
/**
* Find the description of a tocall string.
* @param tocall String tocall to decode
* @return TocallDescription data structure, or null if tocall could not be decoded
*/
public static TocallDescription get(String tocall) {
TocallDescription descr = null;
if (tocall.startsWith("AP")) { // general prefix for standard APRS stations?
//TODO: replace this with JSON tree lookup
for (int len = tocall.length(); len > 2; len--) {
String trimmedTocall = tocall.substring(0, len);
if ((descr = instance.tocallToDescriptionMap.get(trimmedTocall)) != null) {
break;
}
}
} else if (tocall.startsWith("GPS")) {
String symbol;
if ((symbol = Message.extractSymbol(tocall)) != null) {
descr = new TocallDescription(tocall);
descr.model = MessageFormat.format(FMT_GPS, Symbols.getTypeName(symbol.charAt(0), symbol.charAt(1)));
}
} else if (tocall.startsWith("SPC")) {
String symbol;
if ((symbol = Message.extractSymbol(tocall)) != null) {
descr = new TocallDescription(tocall);
descr.model = MessageFormat.format(FMT_SPC, Symbols.getTypeName(symbol.charAt(0), symbol.charAt(1)));
}
}
return descr;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 B

View File

@ -0,0 +1,57 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Configuring YAAC</title>
</head>
<body lang="en-US" dir="LTR">
<h1>Configuring YAAC</h1>
<p>Once YAAC is started, its graphical user interface (GUI) should
appear on your desktop. If you have not previously configured YAAC,
you will need to invoke the configuration option to set up YAAC for
your environment. YAAC may detect the lack of configuration and automatically
ask if you would like to use the <a href="./quickstart.html">configuration wizard</a>. Otherwise, click on the
File menu (or press Alt-F on the keyboard, then
select the Configure option on the menu (or press C). At this point, you have
four options:</p>
<ul>
<li>invoking the <a href="./quickstart.html">Configuration Wizard</a>, which will walk you through setting
up a basic and standard YAAC setup, or</li>
<li>directly accessing the configuration parameters, which gives you detailed
control over all aspects of YAAC, in a manner organized to keep related
data together.</li>
<li>exporting the current YAAC configuration into a XML file, suitable for sending to the
YAAC author to support troubleshooting a problem.</li>
<li>importing a YAAC configuration exported from another system with appropriate adjustments for this station.
Note this option is experimental and may not give the desired results, as it attempts to prevent creating
identical duplicate stations that would interfere with each other on the APRS networks.</li>
</ul>
<p>The Expert Mode choice will open
a dialog with several tabbed panes of configuration options.</p>
<ul>
<li><a href="./config_general.html">General options</a>, which control the basic operation of YAAC.
<li><a href="./config_transmit.html">transmit options</a>, which control how YAAC transmits
messages to other APRS stations.
<li><a href="./config_digipeat.html">digipeat options</a>, which control how YAAC
digipeats messages to other APRS stations.
<li><a href="./config_ports.html">Ports</a>, which configure the interfaces between YAAC and the
outside world. This includes radio/TNC ports, GPS receivers, weather stations, and
Internet connections to APRS-IS servers.
<li><a href="./config_beacon.html">Beacon data</a>, which specifies what information your computer
will announce back to the APRS network.
<li><a href="./config_prefs.html">Display preferences</a>, such as the font for monospaced message displays,
the units for distances and temperatures, etc.
<li><a href="./config_behavior.html">Behavioral preferences</a>, such as whether YAAC should beep if
a message addressed to this station arrives,
whether or not to do dead reckoning of moving stations, and whether or not to do
<a href="./config_behavior.html#vicinity">vicinity plotting</a> for stations not providing position information.
</ul>
<p>Additional tabs may appear if you have installed YAAC <a href="./help.html#plugin">plugins</a> that are configurable.</p>
<p>If you need to use the same copy of YAAC in several different environments with different
configurations, use YAAC's <a href="./profiles.html">configuration profile feature</a> to set up multiple configuration profiles and select the
appropriate one at startup time. Note that the configuration dialog will announce the name of the profile being
used (if any).</p>
</body>
</html>

View File

@ -0,0 +1,2 @@
RXTX.cantFindLibrary=can't find RXTX Java or native library
About.2=uses customized variant of RXTX library version 2.2pre2

View File

@ -0,0 +1,107 @@
package org.ka2ddo.util;
/*
* Copyright (C) 2011-2016 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
/**
* Enumeration of supported units of atmospheric pressure. All scaling factors are in units
* relative to the Peet Bros weather station standard resolution of 0.1 millibars.
*/
public enum PressureUnit implements EnumWithImageName, UnitEnum<PressureUnit> {
/**
* Pressure in pascals.
*/
Pa(0.1F, 0, "Pa", "images/pressure_Pa.png"),
/**
* Pressure in hectopascals (also known as millibars).
*/
HPa(10.0F, 1, "HPa", "images/pressure_hPa.png"), // or millibars
/**
* Pressure in kilopascals (also known as 10's of millibars).
*/
KPa(100.0F, 2, "KPa", "images/pressure_kPa.png"),
/**
* Pressure in millimeters of mercury.
*/
mmHg(13.33224F, 2, "mmHg", "images/pressure_mmHg.png"),
/**
* Pressure in pounds per square inch.
*/
PSI(689.48F, 2, "psi", "images/pressure_psi.png"),
/**
* Pressure in inches of mercury.
*/
inHg(338.639F, 2, "inHg", "images/pressure_inHg.png");
private final float in0_1mbar;
private final int digitsAfterDecimalPoint;
private final String displayName;
private final String imgName;
private PressureUnit(float scaleFactor, int digitsAfterDecimalPoint, String displayName, String imageFileName) {
this.in0_1mbar = scaleFactor;
this.digitsAfterDecimalPoint = digitsAfterDecimalPoint;
this.displayName = displayName;
this.imgName = imageFileName;
}
/**
* Get the multiplicative conversion factor to convert a pressure from the specified
* units to this unit.
* @param other another PressureUnit representing the existing units of a pressure value
* @return the conversion factor to change a pressure value into this unit
*/
public final float from(PressureUnit other) {
return other.in0_1mbar / this.in0_1mbar;
}
/**
* Get the additive offset to convert a value from the specified unit to this unit.
*
* @param other another PressureUnit representing the existing units of a value
* @return the offset (after multiplying by the from() factor) to add to get the value in this unit
*/
public float getOffset(PressureUnit other) {
return 0;
}
/**
* Get the number of digits after the decimal point for displaying in these units.
* @return digits to display after decimal point
*/
public final int getDigitsAfterDecimalPoint() {
return digitsAfterDecimalPoint;
}
/**
* Get the string to use when displaying values of this unit.
* @return suffix display String
*/
public final String unitString() {
return displayName;
}
/**
* Return the relative path name of the image file for this enum value.
*
* @return path name String
*/
public String getImagePath() {
return imgName;
}
}

View File

@ -0,0 +1,83 @@
package org.ka2ddo.yaac.gui.filter;
/*
* Copyright (C) 2011-2019 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.yaac.YAAC;
import org.ka2ddo.yaac.filter.Filter;
import org.ka2ddo.yaac.filter.FilterChangeListener;
import javax.swing.*;
import java.io.Closeable;
import java.io.IOException;
import java.util.ResourceBundle;
import java.awt.event.ItemListener;
import java.awt.event.ItemEvent;
/**
* This filter optionally eliminates all routine precedence messages.
* @author Andrew Pavlin, KA2DDO
*/
public class PriorityFilter extends JPanel implements Closeable, FilterChangeListener {
final transient org.ka2ddo.yaac.filter.PriorityFilter filter;
private final JCheckBox cbPriorityOnly;
/**
* Create a UI for controlling a PriorityFilter instance.
* @param myFilter the PriorityFilter instance to control
*/
public PriorityFilter(org.ka2ddo.yaac.filter.PriorityFilter myFilter) {
this.filter = myFilter;
ResourceBundle msgBundle = YAAC.getMsgBundle();
cbPriorityOnly = new JCheckBox(msgBundle.getString("filter.PriorityFilter.priorityOnly"), filter.isPriorityOnly());
add(cbPriorityOnly);
cbPriorityOnly.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (filter.isPriorityOnly() != cbPriorityOnly.isSelected()) {
filter.setPriorityOnly(cbPriorityOnly.isSelected());
}
}
});
filter.addFilterChangeListener(this);
}
/**
* Closes this stream and releases any system resources associated
* with it. If the stream is already closed then invoking this
* method has no effect.
*
* @throws java.io.IOException if an I/O error occurs
*/
public void close() throws IOException {
filter.removeFilterChangeListener(this);
}
/**
* Called when the specified Filter's matching criteria have been changed.
*
* @param changedFilter Filter that has changed
* @param changedByUser boolean true if change was manually made by user, false if
* change was made automatically by dynamic filter logic
*/
public void filterSettingsChanged(Filter changedFilter, boolean changedByUser) {
boolean newState = filter.isPriorityOnly();
if (newState != cbPriorityOnly.isSelected()) {
cbPriorityOnly.setSelected(newState);
}
}
}

View File

@ -0,0 +1,54 @@
package org.ka2ddo.yaac.pluginapi;
/*
* Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
/**
* This enum defines content types for UI panels that can launch popup menus,
* to help popup menu actions decide whether they can be used in the launching
* context.
* @author Andrew Pavlin, KA2DDO
*/
public enum GuiContentType {
/**
* GuiContentType of a UI displaying only APRS text messages (code ':').
*/
TEXT_MESSAGE,
/**
* GuiContentType of a UI displaying only APRS text messages that are broadcast bulletins.
*/
BULLETIN,
/**
* GuiContentType of a tabular UI displaying the current status of stations and objects.
*/
STATIONS,
/**
* GuiContentType of a tabular UI displaying current severe weather zones.
*/
WEATHER_ZONES,
/**
* GuiContentType of a tabular UI displaying raw frames, with the sending station's callsign in model column 1.
*/
RAW_PACKETS,
/**
* GuiContentType of a geographical map display with stations and objects on it.
*/
MAP
// Any new content types must be added here for backwards compatibility.
}

View File

@ -0,0 +1,57 @@
package org.ka2ddo.yaac.gui.config;
/*
* Copyright (C) 2011-2012 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
/**
* This class renders a list of family-related Font object in a JList.
*/
class FontFamilyRenderer extends JLabel implements ListCellRenderer {
/**
* Return a component that has been configured to display the specified
* value. That component's <code>paint</code> method is then called to
* "render" the cell. If it is necessary to compute the dimensions
* of a list because the list cells do not have a fixed size, this method
* is called to generate a component on which <code>getPreferredSize</code>
* can be invoked.
*
* @param list The JList we're painting.
* @param value The value returned by list.getModel().getElementAt(index).
* @param index The cells index.
* @param isSelected True if the specified cell was selected.
* @param cellHasFocus True if the specified cell has the focus.
* @return A component whose paint() method will render the specified value.
* @see javax.swing.JList
* @see javax.swing.ListSelectionModel
* @see javax.swing.ListModel
*/
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
ArrayList<Font> fList = (ArrayList<Font>)value;
if (null != value) {
setText(fList.get(0).getFamily());
setFont(new Font(fList.get(0).getFamily(), Font.PLAIN, 12));
} else {
setText("");
}
return this;
}
}

View File

@ -0,0 +1,19 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Configure Callsign Database</title>
</head>
<body lang="en-US" dir="LTR">
<h1>Configure Callsign Database</h1>
<p>The Callsign DB tab in the expert-mode configuration dialog allows you to save your authentication
information for <a href="https://www.qrz.com/" target="QRZ">QRZ.COM</a>'s international callsign database (so YAAC can automatically log into QRZ.COM
if needed to complete a lookup), and test that the callsign lookup mechanism is working.</p>
<p>Note that access to QRZ.COM's backend database this way requires purchasing from them
(at minimum) an "XML Logbook Data Subscription" to supplement your account's capabilities.</p>
<p><a name="offline">Note that if you are going to use YAAC offline (without Internet connectivity), you should clear
your QRZ username. This will prevent lookup attempts from hanging trying to access QRZ.COM until
the requests time out. Alternatively, you can check the "Do not use Internet databases for
dynamic lookups" option to temporarily block attempts to reach the Internet services.</a></p>
</body>
</html>

View File

@ -0,0 +1,8 @@
/**
* This package supports the automatic upgrader, that handles installing a freshly-downloaded
* new build of YAAC over a previous build, Note that it runs out of separate JAR files not used by
* YAAC, such that all YAAC files can be overwritten. If this was not the case, operating systems
* such as Microsoft Windows (that prohibit deleting and overwriting files that are open for
* read/execution) would not be able to be upgraded automatically.
*/
package org.ka2ddo.yaac.upgrade;

View File

@ -0,0 +1,480 @@
/*-------------------------------------------------------------------------
| RXTX License v 2.1 - LGPL v 2.1 + Linking Over Controlled Interface.
| RXTX is a native interface to serial ports in java.
| Copyright 1997-2007 by Trent Jarvi tjarvi@qbang.org and others who
| actually wrote it. See individual source files for more information.
|
| A copy of the LGPL v 2.1 may be found at
| http://www.gnu.org/licenses/lgpl.txt on March 4th 2007. A copy is
| here for your convenience.
|
| This library is free software; you can redistribute it and/or
| modify it under the terms of the GNU Lesser General Public
| License as published by the Free Software Foundation; either
| version 2.1 of the License, or (at your option) any later version.
|
| This library 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
| Lesser General Public License for more details.
|
| An executable that contains no derivative of any portion of RXTX, but
| is designed to work with RXTX by being dynamically linked with it,
| is considered a "work that uses the Library" subject to the terms and
| conditions of the GNU Lesser General Public License.
|
| The following has been added to the RXTX License to remove
| any confusion about linking to RXTX. We want to allow in part what
| section 5, paragraph 2 of the LGPL does not permit in the special
| case of linking over a controlled interface. The intent is to add a
| Java Specification Request or standards body defined interface in the
| future as another exception but one is not currently available.
|
| http://www.fsf.org/licenses/gpl-faq.html#LinkingOverControlledInterface
|
| As a special exception, the copyright holders of RXTX give you
| permission to link RXTX with independent modules that communicate with
| RXTX solely through the Sun Microsytems CommAPI interface version 2,
| regardless of the license terms of these independent modules, and to copy
| and distribute the resulting combined work under terms of your choice,
| provided that every copy of the combined work is accompanied by a complete
| copy of the source code of RXTX (the version of RXTX used to produce the
| combined work), being distributed under the terms of the GNU Lesser General
| Public License plus this exception. An independent module is a
| module which is not derived from or based on RXTX.
|
| Note that people who make modified versions of RXTX are not obligated
| to grant this special exception for their modified versions; it is
| their choice whether to do so. The GNU Lesser General Public License
| gives permission to release a modified version without this exception; this
| exception also makes it possible to release a modified version which
| carries forward this exception.
|
| You should have received a copy of the GNU Lesser General Public
| License along with this library; if not, write to the Free
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
| All trademarks belong to their respective owners.
--------------------------------------------------------------------------*/
package gnu.io;
import java.io.*;
import java.util.*;
import java.lang.Math;
/**
* @author Trent Jarvi
* @version $Id: Raw.java,v 1.1.2.17 2008-09-14 22:29:30 jarvi Exp $
* @since JDK1.0
*/
final class Raw extends RawPort {
static
{
SystemAPI.loadLibrary( "rxtxRaw" );
Initialize();
}
/** Initialize the native library */
private native static void Initialize();
/** Actual RawPort wrapper class */
/** Open the named port */
public Raw( String name ) throws PortInUseException {
ciAddress=Integer.parseInt(name);
open( ciAddress );
}
private native int open( int ciAddress ) throws PortInUseException;
/** File descriptor */
private int ciAddress;
/** DSR flag **/
static boolean dsrFlag = false;
/** Output stream */
private final RawOutputStream out = new RawOutputStream();
public OutputStream getOutputStream() { return out; }
/** Input stream */
private final RawInputStream in = new RawInputStream();
public InputStream getInputStream() { return in; }
/** Set the RawPort parameters */
public void setRawPortParams( int b, int d, int s, int p )
throws UnsupportedCommOperationException
{
nativeSetRawPortParams( b, d, s, p );
speed = b;
dataBits = d;
stopBits = s;
parity = p;
}
/** Set the native Raw port parameters */
private native void nativeSetRawPortParams( int speed, int dataBits,
int stopBits, int parity ) throws UnsupportedCommOperationException;
/** Line speed in bits-per-second */
private int speed=9600;
public int getBaudRate() { return speed; }
/** Data bits port parameter */
private int dataBits=DATABITS_8;
public int getDataBits() { return dataBits; }
/** Stop bits port parameter */
private int stopBits=RawPort.STOPBITS_1;
public int getStopBits() { return stopBits; }
/** Parity port parameter */
private int parity= RawPort.PARITY_NONE;
public int getParity() { return parity; }
/** Flow control */
private int flowmode = RawPort.FLOWCONTROL_NONE;
public void setFlowControlMode( int flowcontrol ) {
try { setflowcontrol( flowcontrol ); }
catch( IOException e ) {
e.printStackTrace();
return;
}
flowmode=flowcontrol;
}
public int getFlowControlMode() { return flowmode; }
native void setflowcontrol( int flowcontrol ) throws IOException;
/*
linux/drivers/char/n_hdlc.c? FIXME
taj@www.linux.org.uk
*/
/** Receive framing control
*/
public void enableReceiveFraming( int f )
throws UnsupportedCommOperationException
{
throw new UnsupportedCommOperationException( "Not supported" );
}
public void disableReceiveFraming() {}
public boolean isReceiveFramingEnabled() { return false; }
public int getReceiveFramingByte() { return 0; }
/** Receive timeout control */
private int timeout = 0;
public native int NativegetReceiveTimeout();
public native boolean NativeisReceiveTimeoutEnabled();
public native void NativeEnableReceiveTimeoutThreshold(int time, int threshold,int InputBuffer);
public void disableReceiveTimeout(){
enableReceiveTimeout(0);
}
public void enableReceiveTimeout( int time ){
if( time >= 0 ) {
timeout = time;
NativeEnableReceiveTimeoutThreshold( time , threshold, InputBuffer );
}
else {
System.out.println("Invalid timeout");
}
}
public boolean isReceiveTimeoutEnabled(){
return(NativeisReceiveTimeoutEnabled());
}
public int getReceiveTimeout(){
return(NativegetReceiveTimeout( ));
}
/** Receive threshold control */
private int threshold = 0;
public void enableReceiveThreshold( int thresh ){
if(thresh >=0)
{
threshold=thresh;
NativeEnableReceiveTimeoutThreshold(timeout, threshold, InputBuffer);
}
else /* invalid thresh */
{
System.out.println("Invalid Threshold");
}
}
public void disableReceiveThreshold() {
enableReceiveThreshold(0);
}
public int getReceiveThreshold(){
return threshold;
}
public boolean isReceiveThresholdEnabled(){
return(threshold>0);
}
/** Input/output buffers */
/** FIXME I think this refers to
FOPEN(3)/SETBUF(3)/FREAD(3)/FCLOSE(3)
taj@www.linux.org.uk
These are native stubs...
*/
private int InputBuffer=0;
private int OutputBuffer=0;
public void setInputBufferSize( int size )
{
InputBuffer=size;
}
public int getInputBufferSize()
{
return(InputBuffer);
}
public void setOutputBufferSize( int size )
{
OutputBuffer=size;
}
public int getOutputBufferSize()
{
return(OutputBuffer);
}
/** Line status methods */
public native boolean isDTR();
public native void setDTR( boolean state );
public native void setRTS( boolean state );
private native void setDSR( boolean state );
public native boolean isCTS();
public native boolean isDSR();
public native boolean isCD();
public native boolean isRI();
public native boolean isRTS();
/** Write to the port */
public native void sendBreak( int duration );
private native void writeByte( int b ) throws IOException;
private native void writeArray( byte b[], int off, int len )
throws IOException;
private native void drain() throws IOException;
/** Raw read methods */
private native int nativeavailable() throws IOException;
private native int readByte() throws IOException;
private native int readArray( byte b[], int off, int len )
throws IOException;
/** Raw Port Event listener */
private RawPortEventListener SPEventListener;
/** Thread to monitor data */
private MonitorThread monThread;
/** Process RawPortEvents */
native void eventLoop();
private int dataAvailable=0;
public void sendEvent( int event, boolean state ) {
switch( event ) {
case RawPortEvent.DATA_AVAILABLE:
dataAvailable=1;
if( monThread.Data ) break;
return;
case RawPortEvent.OUTPUT_BUFFER_EMPTY:
if( monThread.Output ) break;
return;
/*
if( monThread.DSR ) break;
return;
if (isDSR())
{
if (!dsrFlag)
{
dsrFlag = true;
RawPortEvent e = new RawPortEvent(this, RawPortEvent.DSR, !dsrFlag, dsrFlag );
}
}
else if (dsrFlag)
{
dsrFlag = false;
RawPortEvent e = new RawPortEvent(this, RawPortEvent.DSR, !dsrFlag, dsrFlag );
}
*/
case RawPortEvent.CTS:
if( monThread.CTS ) break;
return;
case RawPortEvent.DSR:
if( monThread.DSR ) break;
return;
case RawPortEvent.RI:
if( monThread.RI ) break;
return;
case RawPortEvent.CD:
if( monThread.CD ) break;
return;
case RawPortEvent.OE:
if( monThread.OE ) break;
return;
case RawPortEvent.PE:
if( monThread.PE ) break;
return;
case RawPortEvent.FE:
if( monThread.FE ) break;
return;
case RawPortEvent.BI:
if( monThread.BI ) break;
return;
default:
System.err.println("unknown event:"+event);
return;
}
RawPortEvent e = new RawPortEvent(this, event, !state, state );
if( SPEventListener != null ) SPEventListener.RawEvent( e );
}
/** Add an event listener */
public void addEventListener( RawPortEventListener lsnr )
throws TooManyListenersException
{
if( SPEventListener != null ) throw new TooManyListenersException();
SPEventListener = lsnr;
monThread = new MonitorThread();
monThread.start();
}
/** Remove the Raw port event listener */
public void removeEventListener() {
SPEventListener = null;
if( monThread != null ) {
monThread.interrupt();
monThread = null;
}
}
public void notifyOnDataAvailable( boolean enable ) { monThread.Data = enable; }
public void notifyOnOutputEmpty( boolean enable ) { monThread.Output = enable; }
public void notifyOnCTS( boolean enable ) { monThread.CTS = enable; }
public void notifyOnDSR( boolean enable ) { monThread.DSR = enable; }
public void notifyOnRingIndicator( boolean enable ) { monThread.RI = enable; }
public void notifyOnCarrierDetect( boolean enable ) { monThread.CD = enable; }
public void notifyOnOverrunError( boolean enable ) { monThread.OE = enable; }
public void notifyOnParityError( boolean enable ) { monThread.PE = enable; }
public void notifyOnFramingError( boolean enable ) { monThread.FE = enable; }
public void notifyOnBreakInterrupt( boolean enable ) { monThread.BI = enable; }
/** Close the port */
private native int nativeClose();
public void close() {
setDTR(false);
setDSR(false);
nativeClose();
super.close();
ciAddress = 0;
}
/** Finalize the port */
protected void finalize() {
close();
}
/** Inner class for RawOutputStream */
class RawOutputStream extends OutputStream {
public void write( int b ) throws IOException {
writeByte( b );
}
public void write( byte b[] ) throws IOException {
writeArray( b, 0, b.length );
}
public void write( byte b[], int off, int len ) throws IOException {
writeArray( b, off, len );
}
public void flush() throws IOException {
drain();
}
}
/** Inner class for RawInputStream */
class RawInputStream extends InputStream {
public int read() throws IOException {
dataAvailable=0;
return readByte();
}
public int read( byte b[] ) throws IOException
{
return read ( b, 0, b.length);
}
public int read( byte b[], int off, int len ) throws IOException
{
dataAvailable=0;
int i=0, Minimum=0;
int intArray[] =
{
b.length,
InputBuffer,
len
};
/*
find the lowest nonzero value
timeout and threshold are handled on the native side
see NativeEnableReceiveTimeoutThreshold in
RawImp.c
*/
while(intArray[i]==0 && i < intArray.length) i++;
Minimum=intArray[i];
while( i < intArray.length )
{
if(intArray[i] > 0 )
{
Minimum=Math.min(Minimum,intArray[i]);
}
i++;
}
Minimum=Math.min(Minimum,threshold);
if(Minimum == 0) Minimum=1;
int Available=available();
int Ret = readArray( b, off, Minimum);
return Ret;
}
public int available() throws IOException {
return nativeavailable();
}
}
class MonitorThread extends Thread {
/** Note: these have to be separate boolean flags because the
RawPortEvent constants are NOT bit-flags, they are just
defined as integers from 1 to 10 -DPL */
private boolean CTS=false;
private boolean DSR=false;
private boolean RI=false;
private boolean CD=false;
private boolean OE=false;
private boolean PE=false;
private boolean FE=false;
private boolean BI=false;
private boolean Data=false;
private boolean Output=false;
MonitorThread() { }
public void run() {
eventLoop();
}
}
public String getVersion()
{
String Version="$Id: Raw.java,v 1.1.2.17 2008-09-14 22:29:30 jarvi Exp $";
return(Version);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,55 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Download Topographic Tiles</title>
</head>
<body lang="en-US" dir="LTR">
<h1>Download Topographic Tiles</h1>
<p>YAAC supports topographic data rendered from the NASA Digital Elevation Model.
The <a name="NASA">NASA Digital Elevation Model data is retrieved from the online Data Pool,
courtesy of the NASA EOSDIS Land Processes Distributed Active Archive Center
(LP DAAC),</a> USGS Earth Resources Observation and Science (EROS) Center, Sioux
Falls, South Dakota,
<a href="https://lpdaac.usgs.gov/tools/data-pool/" target="NASA">https://lpdaac.usgs.gov/tools/data-pool/</a>.
A downloader is built into YAAC for your convenience.
All you have to specify what area of the planet you want to download,
and YAAC will do it for you. Note you must have connectivity to the global Internet for
this to work, and an account on the NASA Earthdata data pool web service.
If you do not already have such an account, go to
<a href="https://urs.earthdata.nasa.gov/" target="NASA">the
User Registration Site</a> for Earthlink to create your free account, and then request
access to the LP DAAC Data Pool application.</p>
<p>Upon selecting the
File-&gt;Topographic-&gt;Download Tiles option, a
dialog box will be displayed showing what map data (if any) you have.</p>
<img src="./downloadsrtm1.png" alt="dialog for selecting tiles to download"/>
<p>The dialog shows yellow dots
where you have data downloaded, and dark gray dots where you do not.
The red box identifies the area that will be downloaded should you
choose to proceed. You can move and resize the red box by changing
the center latitude and longitude parameters, and the radius
parameter.</p>
<p>You can also choose to download higher resolution (1-arc-second) topographic
data. Currently, this data is only available for the United States
and its territories. Note the higher-resolution data will take up approximately 9 times
as much disk space as the default 3-arc-second data that is available
world-wide, and will also take longer to render topographic information
on the map. Choose this only if you have a need for such precision data
(such as for Search and Rescue operations).</p>
<p>If you click “OK”, YAAC will bring
up a progress dialog reporting how far through the download process
you are and which file it is downloading at any given point in time, then prompt
you for your Earthdata login credentials.
The progress dialog will automatically go away when the download is complete.
Note that, if you stop YAAC in the middle of the download, you will
only have what was successfully downloaded up to that point. You can
also prematurely cancel the download by clicking the “Cancel”
button on the progress dialog. Note the cancel will wait until the
current tile file is complete before actually stopping.</p>
<img src="./downloadsrtm2.png" alt="download progress dialog"/>
<p>Once the topographic elevation data is downloaded, you can choose how to display
it from the <a href="./viewmaplayers.html#terrain">View-&gt;Layers...-&gt;Show Topography menu choice</a>,
or use the elevation data in generating line-of-sight plots.</p>
</body>
</html>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE index
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Index Version 2.0//EN"
"http://java.sun.com/products/javahelp/index_2_0.dtd">
<!--
Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
This file is part of YAAC (Yet Another APRS Client).
YAAC is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
YAAC 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
and GNU Lesser General Public License along with YAAC. If not,
see <http://www.gnu.org/licenses/>.
-->
<index version="2.0">
<indexitem text="configuring YAAC">
<indexitem text="weather alerts" target="configure.WeatherAlert"/>
</indexitem>
<indexitem text="email">
<indexitem text="weather alerts by" target="configure.WeatherAlert.email"/>
</indexitem>
<indexitem text="MADIS quality checks">
<indexitem text="reporting quality failures" target="configure.WeatherAlert.MADIS"/>
</indexitem>
<indexitem text="speech">
<indexitem text="weather alerts by" target="configure.WeatherAlert.speech"/>
</indexitem>
<indexitem text="weather">
<indexitem text="alerts" target="configure.WeatherAlert"/>
</indexitem>
</index>

View File

@ -0,0 +1,421 @@
package org.ka2ddo.yaac.gui.filter;
/*
* Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.yaac.ax25.*;
import org.ka2ddo.yaac.aprs.*;
import org.ka2ddo.yaac.YAAC;
import org.ka2ddo.yaac.core.PacketExportMode;
import org.ka2ddo.yaac.filter.Filter;
import org.ka2ddo.yaac.filter.FilterChangeListener;
import org.ka2ddo.yaac.gui.SaveableFilter;
import org.ka2ddo.yaac.gui.table.FastComparableTableModel;
import org.ka2ddo.yaac.gui.table.FastTableRowSorter;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.*;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.PrintStream;
import java.text.MessageFormat;
import java.util.*;
/**
* This filter accepts or rejects messages based on the last station to transmit
* the message.
* @author Andrew Pavlin, KA2DDO
*/
public class LastDigipeatFilterUI extends JPanel implements TableModel, DigipeatListener, SaveableFilter, Closeable, FilterChangeListener, FastComparableTableModel {
private static final Class[] COLUMN_CLASSES = { String.class, Boolean.class, Integer.class };
private static final String[] COLUMN_NAMES = new String[COLUMN_CLASSES.length];
private final transient org.ka2ddo.yaac.filter.LastDigipeatFilter filter;
final transient StationTracker TRACKER = StationTracker.getInstance();
private final ArrayList<StationTracker.DigipeatStatistics> digiListCopy = new ArrayList<>();
private final transient ArrayList<TableModelListener> listeners = new ArrayList<TableModelListener>();
private static final java.util.List<RowSorter.SortKey> INITIAL_SORT = new ArrayList<RowSorter.SortKey>(1);
private final MessageFormat mfNumDigis;
private final JLabel lNumDigis;
static {
INITIAL_SORT.add(new RowSorter.SortKey(0, SortOrder.ASCENDING));
}
/**
* Create a UI for controlling a LastDigipeatFilter
* @param myFilter the LastDigipeatFilter instance to control
* @see org.ka2ddo.yaac.filter.LastDigipeatFilter
*/
public LastDigipeatFilterUI(org.ka2ddo.yaac.filter.LastDigipeatFilter myFilter) {
super(new BorderLayout());
this.filter = myFilter;
ResourceBundle msgBundle = YAAC.getMsgBundle();
JPanel header = new JPanel(new FlowLayout(FlowLayout.CENTER));
JButton bSetAll = new JButton(msgBundle.getString("SymbolFilter.setAll"));
bSetAll.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
boolean changed = false;
int row = 0;
for (StationTracker.DigipeatStatistics ds : filter.getDigipeatCache()) {
if (!ds.enabled) {
ds.enabled = true;
changed = true;
TableModelEvent tme = new TableModelEvent(LastDigipeatFilterUI.this, row, row, 1, TableModelEvent.UPDATE);
for (TableModelListener l : listeners) {
l.tableChanged(tme);
}
}
row++;
}
if (changed) {
filter.fireFilterChange(true);
}
}
});
header.add(bSetAll);
JButton bClearAll = new JButton(msgBundle.getString("SymbolFilter.clearAll"));
bClearAll.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
boolean changed = false;
int row = 0;
for (StationTracker.DigipeatStatistics ds : filter.getDigipeatCache()) {
if (ds.enabled) {
ds.enabled = false;
changed = true;
TableModelEvent tme = new TableModelEvent(LastDigipeatFilterUI.this, row, row, 1, TableModelEvent.UPDATE);
for (TableModelListener l : listeners) {
l.tableChanged(tme);
}
}
row++;
}
if (changed) {
filter.fireFilterChange(true);
}
}
});
header.add(bClearAll);
header.add(lNumDigis = new JLabel(" "));
mfNumDigis = new MessageFormat(msgBundle.getString("filter.Digipeat.numDigis"));
add(BorderLayout.NORTH, header);
COLUMN_NAMES[0] = msgBundle.getString("filter.Digipeat.Station");
COLUMN_NAMES[1] = msgBundle.getString("filter.Digipeat.Enabled");
COLUMN_NAMES[2] = msgBundle.getString("filter.Digipeat.Usage");
fillInDigiTotals();
TRACKER.addRelayListener(this);
digiListCopy.addAll(filter.getDigipeatCache());
JTable table;
add(BorderLayout.CENTER, new JScrollPane(table = new JTable(this)));
RowSorter<LastDigipeatFilterUI> sorter;
table.setRowSorter(sorter = new FastTableRowSorter<LastDigipeatFilterUI>(this));
sorter.setSortKeys(INITIAL_SORT);
filter.addFilterChangeListener(this);
}
/**
* Returns the number of columns in the model.
*
* @return the number of columns in the model
* @see #getRowCount
*/
public int getColumnCount() {
return COLUMN_CLASSES.length;
}
/**
* Returns the number of rows in the model.
*
* @return the number of rows in the model
* @see #getColumnCount
*/
public int getRowCount() {
return digiListCopy.size();
}
/**
* Returns the value for the cell at <code>columnIndex</code> and
* <code>rowIndex</code>.
*
* @param rowIndex the row whose value is to be queried
* @param columnIndex the column whose value is to be queried
* @return the value Object at the specified cell
*/
public Object getValueAt(int rowIndex, int columnIndex) {
StationTracker.DigipeatStatistics entry = filter.getDigipeatCache().get(rowIndex);
switch (columnIndex) {
case 0:
return entry.getKey();
case 1:
return entry.getValue().enabled;
case 2:
return entry.getValue().numUsed;
default:
throw new IndexOutOfBoundsException("" + columnIndex);
}
}
/**
* Compare the selected column of the two rows.
*
* @param rowIndex1 zero-based model row index of first row to compare
* @param rowIndex2 zero-based model row index of second row to compare
* @param columnIndex zero-based model column index of column to compare
* @return +1 if the 1st row's column is after the 2nd row's column, -1 if before, or 0 if equal precedence
*/
public int compareRows(int rowIndex1, int rowIndex2, int columnIndex) {
ArrayList<StationTracker.DigipeatStatistics> digipeatCache = filter.getDigipeatCache();
StationTracker.DigipeatStatistics entry1 = digipeatCache.get(rowIndex1);
StationTracker.DigipeatStatistics entry2 = digipeatCache.get(rowIndex2);
switch (columnIndex) {
case 0:
return entry1.getKey().compareTo(entry2.getKey());
case 1:
return entry1.getValue().enabled ? (entry2.getValue().enabled ? 0 : -1) : (entry2.getValue().enabled ? +1 : 0);
case 2:
return entry1.getValue().numUsed - entry2.getValue().numUsed;
default:
throw new IndexOutOfBoundsException("" + columnIndex);
}
}
/**
* Returns the data Class for the specified column.
*
* @param columnIndex the column being queried
* @return the Class object for the column's data
*/
@Override
public Class<?> getColumnClass(int columnIndex) {
return COLUMN_CLASSES[columnIndex];
}
/**
* Returns the localized name for the column.
*
* @param column the column being queried
* @return a string containing the name of <code>column</code>
*/
@Override
public String getColumnName(int column) {
return COLUMN_NAMES[column];
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return 1 == columnIndex;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
StationTracker.DigipeatStatistics entry = filter.getDigipeatCache().get(rowIndex);
switch (columnIndex) {
case 1:
entry.getValue().enabled = (Boolean)aValue;
fireTableModelEvent(new TableModelEvent(LastDigipeatFilterUI.this, rowIndex, rowIndex,
columnIndex, TableModelEvent.UPDATE));
filter.fireFilterChange(true);
fillInDigiTotals();
break;
case 0:
case 2:
default:
throw new IndexOutOfBoundsException("" + columnIndex);
}
}
void fillInDigiTotals() {
ArrayList<StationTracker.DigipeatStatistics> digiList = filter.getDigipeatCache();
int size = digiList.size();
int numEnabled = 0;
for (int i = size - 1; i >= 0; i--) {
if (digiList.get(i).enabled) {
numEnabled++;
}
}
lNumDigis.setText(mfNumDigis.format(new Object[]{numEnabled, size}));
}
public void digipeaterAdded(final String digipeat) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final StationTracker.DigipeatStatistics rs;
if ((rs = TRACKER.getDigipeaterStatisticsFor(digipeat)) != null) {
int index;
if ((index = digiListCopy.indexOf(rs)) < 0) {
index = digiListCopy.size();
digiListCopy.add(rs);
}
fireTableModelEvent(new TableModelEvent(LastDigipeatFilterUI.this, index, index,
TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
fillInDigiTotals();
}
}
});
}
public void digipeaterUsedAgain(final String digipeat) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final StationTracker.DigipeatStatistics rs;
int index;
if ((rs = TRACKER.getDigipeaterStatisticsFor(digipeat)) != null) {
if ((index = digiListCopy.indexOf(rs)) >= 0) {
fireTableModelEvent(new TableModelEvent(LastDigipeatFilterUI.this, index, index,
2, TableModelEvent.UPDATE));
} else {
index = digiListCopy.size();
digiListCopy.add(rs);
fireTableModelEvent(new TableModelEvent(LastDigipeatFilterUI.this, index, index,
TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
fillInDigiTotals();
}
}
}
});
}
/**
* Indicate if this Filter is saveable. Meant for use by combining filters whose
* sub-Filters may not all be savable.
*
* @return boolean true if the current Filter can be saved
*/
public boolean isSaveable() {
return true;
}
/**
* Specify the preferred filetype for files saving this Filter's data set.
*
* @return FileNameExtensionFilter that will be used in the saving JFileChooser
*/
public FileNameExtensionFilter getPreferredFileType() {
return new FileNameExtensionFilter(PacketExportMode.CSV.toString(), PacketExportMode.CSV.getFileType());
}
/**
* Save the contents of the Filter to the specified DataOutput object.
*
* @param out DataOutput implementing object for writing the file contents in its preferred format
* @throws java.io.IOException if the write failed for some reason
*/
public void saveFilterToFile(BufferedOutputStream out) throws IOException {
JTable table = null;
for (TableModelListener l : listeners) {
if (l instanceof JTable) {
table = (JTable)l;
break;
}
}
PrintStream ps = new PrintStream(out, false, "US-ASCII");
StringBuilder b = new StringBuilder();
for (int row = 0; row < getRowCount(); row++) {
int sortedRow = table.convertRowIndexToModel(row);
for (int col = 0; col < getColumnCount(); col++) {
if (col != 0) {
ps.print(",");
}
Object valueAt = getValueAt(sortedRow, col);
if (null != valueAt) {
if (valueAt instanceof String) {
String v = (String)valueAt;
if (v.indexOf('"') >= 0 || v.indexOf(',') >= 0 || v.indexOf('\'') >= 0 ||
v.indexOf('-') >= 0 || v.indexOf('/') >= 0 || v.indexOf('*') >= 0 ||
v.indexOf('+') >= 0) {
// string needs to be quoted
b.append('"');
for (int i = 0; i < v.length(); i++) {
char ch = v.charAt(i);
if ('"' == ch) {
b.append("\"\"");
} else {
b.append(ch);
}
}
valueAt = b.append('"').toString();
b.setLength(0);
}
}
ps.print(valueAt);
}
}
ps.println();
}
}
/**
* Closes this stream and releases any system resources associated
* with it. If the stream is already closed then invoking this
* method has no effect.
*
* @throws java.io.IOException if an I/O error occurs
*/
public void close() throws IOException {
TRACKER.removeRelayListener(this);
filter.removeFilterChangeListener(this);
}
/**
* Called when the specified Filter's matching criteria have been changed.
*
* @param changedFilter Filter that has changed
* @param changedByUser boolean true if change was manually made by user, false if
* change was made automatically by dynamic filter logic
*/
public void filterSettingsChanged(Filter changedFilter, boolean changedByUser) {
if (changedFilter == filter && changedByUser) {
fireTableModelEvent(new TableModelEvent(this));
}
}
/**
* Adds a listener to the list that is notified each time a change
* to the data model occurs.
*
* @param l the TableModelListener
*/
public void addTableModelListener(TableModelListener l) {
if (!listeners.contains(l)) {
listeners.add(l);
}
}
/**
* Removes a listener from the list that is notified each time a
* change to the data model occurs.
*
* @param l the TableModelListener
*/
public void removeTableModelListener(TableModelListener l) {
listeners.remove(l);
}
void fireTableModelEvent(TableModelEvent event) {
ArrayList<TableModelListener> listeners = this.listeners;
for (int i = listeners.size() - 1; i >= 0; i--) {
listeners.get(i).tableChanged(event);
}
}
}

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE helpset
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 2.0//EN"
"http://java.sun.com/products/javahelp/helpset_2_0.dtd">
<helpset version="2.0">
<!-- title -->
<title>ADS-B Port Driver</title>
<!-- maps -->
<maps>
<homeID>ADS-B</homeID>
<mapref location="AdsBHelpMap.jhm"/>
</maps>
<!-- views -->
<view mergetype="javax.help.UniteAppendMerge">
<name>TOC</name>
<label>Table Of Contents</label>
<type>javax.help.TOCView</type>
<data>AdsBHelpTOC.xml</data>
</view>
<view mergetype="javax.help.SortMerge">
<name>Index</name>
<label>Index</label>
<type>javax.help.IndexView</type>
<data>AdsBHelpIndex.xml</data>
</view>
<presentation xml:lang="en" default="true" displayviewimages="false">
<name>main window</name>
<size width="700" height="400" />
<location x="200" y="200" />
<title>YAAC - Online Help</title>
<image>icon</image>
<toolbar>
<helpaction>javax.help.BackAction</helpaction>
<helpaction>javax.help.ForwardAction</helpaction>
<helpaction>javax.help.SeparatorAction</helpaction>
<helpaction>javax.help.HomeAction</helpaction>
<helpaction>javax.help.ReloadAction</helpaction>
<helpaction>javax.help.SeparatorAction</helpaction>
<helpaction>javax.help.PrintAction</helpaction>
<helpaction>javax.help.PrintSetupAction</helpaction>
<helpaction>javax.help.SeparatorAction</helpaction>
<helpaction>javax.help.FavoritesAction</helpaction>
</toolbar>
</presentation>
</helpset>

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE toc
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp TOC Version 2.0//EN"
"http://java.sun.com/products/javahelp/toc_2_0.dtd">
<!--
Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
This file is part of YAAC (Yet Another APRS Client).
YAAC is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
YAAC 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
and GNU Lesser General Public License along with YAAC. If not,
see <http://www.gnu.org/licenses/>.
-->
<toc version="2.0">
<tocitem text="YAAC Legacy RXTX Port Drivers" image="toplevelfolder" target="legacyRXTX">
<tocitem text="Serial TNC Port" target="configure.ports.SerialTNC"/>
<tocitem text="Serial GPS Port" target="configure.ports.SerialGPS"/>
<tocitem text="Kenwood radio with built-in TNC" target="configure.ports.Kenwood"/>
<tocitem text="Yaesu radio with built-in TNC" target="configure.ports.Yaesu"/>
<tocitem text="Peet Bros Weather Station" target="configure.ports.Weather"/>
</tocitem>
</toc>

View File

@ -0,0 +1,58 @@
<project name="BikeStats" basedir="." default="BIKESTATS_JAR">
<property name="YAAC.root" location=".."/>
<property name="plugin.root" location="."/>
<property name="YAAC.classes" location="${YAAC.root}/YAAC/target/classes"/>
<property name="target" location="${plugin.root}/target"/>
<property name="classes" location="${target}/classes"/>
<property name="src" location="${plugin.root}/src/main/java"/>
<property name="resrc" location="${plugin.root}/src/main/resources"/>
<property name="compiler" value="modern"/>
<property name="source" value="1.6"/>
<property name="lib.dir" value="${YAAC.root}/lib"/>
<property name="client.jar" value="${YAAC.root}/YAAC/target/YAAC.jar"/>
<property name="bikestats.jar" value="${plugin.root}/target/bikestats.jar"/>
<property name="javadoc.dir" value="${plugin.root}/site/apidocs"/>
<property environment="env"/>
<path id="YAAC.classpath">
<pathelement path="${classes}"/>
<pathelement path="${YAAC.classes}"/>
<fileset dir="${lib.dir}" includes="*.jar"/>
</path>
<target name="CLEAN">
<delete dir="${classes}"/>
<delete file="${bikestats.jar}"/>
</target>
<target name="BIKESTATS_JAVAC">
<mkdir dir="${classes}"/>
<depend cache="depend_cache" srcdir="${src}" destdir="${classes}"/>
<javac encoding="utf8" compiler="${compiler}" source="${source}" target="${source}" classpathref="YAAC.classpath" srcdir="${src}" debug="On" debuglevel="lines,vars,source" destdir="${classes}">
<!--compilerarg value="-Xlint:unchecked"/-->
</javac>
</target>
<target name="BIKESTATS_COPY">
<copy todir="${classes}/org/ka2ddo/yaac/bikestats">
<fileset dir="${resrc}/org/ka2ddo/yaac/bikestats">
</fileset>
</copy>
</target>
<target name="BIKESTATS_JAVADOC">
<javadoc encoding="utf8" sourcepath="${src}"
protected="yes" package="no" private="no"
destdir="${javadoc.dir}" classpathref="YAAC.classpath">
</javadoc>
</target>
<target name="BIKESTATS_JAR" depends="BIKESTATS_JAVAC,BIKESTATS_COPY">
<jar destfile="${bikestats.jar}" basedir="${classes}" update="No" manifest="${src}/../Manifest.txt">
<include name="org/ka2ddo/yaac/**"/>
</jar>
</target>
</project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,518 @@
package org.ka2ddo.yaac.gui.rastermap;
/*
* Copyright (C) 2011-2021 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.apache.commons.imaging.FormatCompliance;
import org.apache.commons.imaging.ImagingException;
import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream;
import org.apache.commons.imaging.formats.tiff.TiffContents;
import org.apache.commons.imaging.formats.tiff.TiffDirectory;
import org.apache.commons.imaging.formats.tiff.TiffReader;
import org.apache.commons.imaging.formats.tiff.constants.GeoTiffTagConstants;
import org.ka2ddo.util.DebugCtl;
import org.ka2ddo.util.ReschedulableTimer;
import org.ka2ddo.util.ReschedulableTimerTask;
import org.ka2ddo.yaac.YAAC;
import org.ka2ddo.yaac.core.ErrorLogger;
import org.ka2ddo.yaac.core.provider.CoreProvider;
import org.ka2ddo.yaac.gui.GeographicalMap;
import org.ka2ddo.util.NonshareableBufferedDataInputStream;
import org.ka2ddo.yaac.io.NonshareableDataInputStream;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.prefs.Preferences;
import java.util.zip.GZIPInputStream;
/**
* This class maintains a catalog of registered raster map images that can be superimposed
* over the MapBean. Each image has its RasterMapEntry describing the image.
* @see RasterMapEntry
* @author Andrew Pavlin, KA2DDO
*/
public final class RasterMapCatalog extends AbstractTableModel {
private static final Class[] COLUMN_CLASSES = { String.class, Boolean.class, Integer.class, Boolean.class };
private static final String[] COLUMN_NAMES = new String[COLUMN_CLASSES.length];
/**
* Prefix of URLs used to download radar mosaics from US National Weather Service.
*/
public static final String HTTPS_RADAR_WEATHER_PREFIX = "https://mrms.ncep.noaa.gov/data/RIDGEII/L2/";
/**
* Name of Java Preferences node containing RasterMapEntry persistence information.
*/
public static final String RASTER_MAP_PREFS_NODE = "RasterMaps";
static final int MIN_DELAY_BEFORE_REFRESH_MSEC = 300000;
/**
* Collection of raster images to overlay in the containing RasterMapOverlay.
*/
public ArrayList<RasterMapEntry> imageList = new ArrayList<RasterMapEntry>();
final RasterMapOverlay rasterMapOverlay;
final GeographicalMap geoMap;
private static final ReschedulableTimer reloadTimer = new ReschedulableTimer("Raster Image reload timer");
static class MapBounds {
final double minLat, maxLat, minLon, maxLon;
final String dirName;
MapBounds(double minLat, double maxLat, double minLon, double maxLon, String dirName) {
this.minLat = minLat;
this.maxLat = maxLat;
this.minLon = minLon;
this.maxLon = maxLon;
this.dirName = dirName;
}
boolean intersects(double minLat, double maxLat, double minLon, double maxLon) {
return (maxLon > this.minLon) &&
(maxLat > this.minLat) &&
(this.maxLon > minLon) &&
(this.maxLat > minLat);
}
}
static final MapBounds[] MAP_REGIONS = {
new MapBounds(24, 50, -128, -64, "CONUS"),
new MapBounds(50, 72, -176, -126, "ALASKA"),
new MapBounds(13, 15, 144, 146, "GUAM"),
new MapBounds(15, 32, -180, -154, "HAWAII"),
new MapBounds(10, 25, -90, -60, "CARIB")
};
/**
* TimerTask for reloading updated NWS weather raster.
*/
public transient ReschedulableTimerTask WXRASTER_TIMERTASK = new ReschedulableTimerTask() {
public void run() {
RasterMapEntry rme = null;
try {
if (CoreProvider.getInstance().isNonencryptedHTTPForced()) {
throw new IOException("unable to access secure website, YAAC is configured to prohibit encryption");
}
// search for any pre-existing copy of the image
long now = System.currentTimeMillis();
Date expireTime = new Date(now);
for (int idx = 0; idx < imageList.size(); idx++) {
RasterMapEntry testRme = imageList.get(idx);
if (testRme.enabled && testRme.pathname.startsWith(HTTPS_RADAR_WEATHER_PREFIX)) {
rme = testRme;
rme.pinpointList.clear();
if (rme.imgCreationDate != null && expireTime.before(rme.imgCreationDate)) {
expireTime.setTime(rme.imgCreationDate.getTime());
} else {
Date tmpExpireTime = loadReloadableWebMap(rme, now); // note completion of this enables the raster
if (expireTime.before(tmpExpireTime)) {
expireTime.setTime(tmpExpireTime.getTime());
}
}
// have to keep looping as there might be more than one of them
}
}
if (rme == null) {
// see which ones we need based on the bounding box of our map
final int oldNumImgs = imageList.size();
double minLat = geoMap.getBottomLatitude();
double maxLat = geoMap.getTopLatitude();
double minLon = geoMap.getLeftLongitude();
double maxLon = geoMap.getRightLongitude();
for (MapBounds mb : MAP_REGIONS) {
if (mb.intersects(minLat, maxLat, minLon, maxLon)) {
rme = new RasterMapEntry();
rme.pathname = HTTPS_RADAR_WEATHER_PREFIX + mb.dirName + "/CREF_QCD/";
rme.isLinear = true;
rme.keepImg = true;
rme.enabled = true;
rme.persist = true;
imageList.add(rme);
rme.writeToPreferences(YAAC.getPreferences().node(RASTER_MAP_PREFS_NODE));
Date tmpExpireTime = loadReloadableWebMap(rme, now); // note completion of this enables the raster
if (expireTime.before(tmpExpireTime)) {
expireTime.setTime(tmpExpireTime.getTime());
}
}
}
final int newNumImgs = imageList.size();
if (newNumImgs > oldNumImgs) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
fireTableRowsInserted(oldNumImgs, newNumImgs-1);
}
});
}
}
// in case we need to cancel it
if (rme != null) {
rme.reloadTimerTask = this;
}
// add the image to the raster list
rasterMapOverlay.setShowRasterMaps(true);
rasterMapOverlay.startRegenerate();
geoMap.refresh();
// schedule fetching an update of the map
if (expireTime.getTime() > now) {
int delay = Math.max((int) (expireTime.getTime() - now + 5000 + Math.random() * 10000), MIN_DELAY_BEFORE_REFRESH_MSEC); // wait at least 3 minutes before trying
if (DebugCtl.isDebug("raster")) System.out.println("scheduling NWS radar overlay reload of " + rme.pathname + " in " + delay + " millisec");
resched(reloadTimer, delay);
} else if (rme != null) {
System.out.println(new Date().toString() + ": somehow reloadable maps are expired before download (" +
expireTime.toString() + ')');
}
} catch (Exception e1) {
if (rme != null) {
ErrorLogger.reportError(geoMap, e1, MessageFormat.format(YAAC.getMsg("RasterMap.UnableToLoadImage"), rme.pathname));
}
}
}
};
/**
* Create a RasterMapCatalog instance for a given GeographicalMap window's RasterMapOverlay.
* @param geoMap GeographicalMap using this instance of RasterMapCatalog
* @param rasterMapOverlay RasterMapOverlay using this instance of RasterMapCatalog
*/
public RasterMapCatalog(GeographicalMap geoMap, final RasterMapOverlay rasterMapOverlay) {
this.geoMap = geoMap;
this.rasterMapOverlay = rasterMapOverlay;
rasterMapOverlay.rasterMapCatalog = this;
boolean hasEnabledRasters = false;
Preferences rootNode = YAAC.getPreferences();
try {
if (rootNode.nodeExists(RASTER_MAP_PREFS_NODE)) {
Preferences rasterNode = rootNode.node(RASTER_MAP_PREFS_NODE);
for (String key : rasterNode.keys()) {
byte[] data = rasterNode.getByteArray(key, null);
if (data != null && data.length > 0) {
final RasterMapEntry rme = new RasterMapEntry();
rme.pathname = key;
NonshareableDataInputStream dis = new NonshareableDataInputStream(new ByteArrayInputStream(data));
rme.read(dis);
dis.close();
if (rme.pathname.startsWith("https:") ||
rme.pathname.startsWith("http:")) {
imageList.add(rme);
if (rme.enabled) {
hasEnabledRasters = true;
// run this in the background in case remote webserver is slow when YAAC is starting up
if (!WXRASTER_TIMERTASK.isActive()) {
WXRASTER_TIMERTASK.resched(reloadTimer, 5000); // wait 5 seconds to avoid collisions
}
}
} else if (new File(rme.pathname).exists()) {
imageList.add(rme);
hasEnabledRasters |= rme.enabled;
} else {
System.out.println("saved RasterMapEntry cannot be restored because file is missing: " + key);
rasterNode.remove(key);
continue;
}
System.out.println(new Date().toString() + ": RasterMapCatalog: reloading persisted raster image " + rme);
}
}
}
} catch (Exception e) {
e.printStackTrace(System.out);
}
if (hasEnabledRasters) {
rasterMapOverlay.setShowRasterMaps(true);
rasterMapOverlay.startRegenerate();
geoMap.refresh();
}
COLUMN_NAMES[0] = YAAC.getMsg("RasterMap.table.ImageName");
COLUMN_NAMES[1] = YAAC.getMsg("RasterMap.table.Enable");
COLUMN_NAMES[2] = YAAC.getMsg("RasterMap.table.Transparency");
COLUMN_NAMES[3] = YAAC.getMsg("RasterMap.table.Persist");
}
/**
* Load a fresh gzipped raster GeoTIFF image from a web source.
* @param rme RasterMapEntry for the web-sourced overlay layer
* @param now current time in milliseconds since Jan 1970 UTC
* @return the Date when the loaded raster image will expire and should be loaded again
* @throws IOException if image file cannot be downloaded
*/
public static Date loadReloadableWebMap(RasterMapEntry rme, long now) throws IOException {
// temporarily show this as disabled to prevent render attempts before everything is loaded
rme.enabled = false; // prevent loading race condition when map is re-rendered while still reading the world file
Date expireTime;
HttpURLConnection urlConnection = null;
try {
// load the directory listing to find the most recent image file
long startTime = System.currentTimeMillis();
URL dirUrl = new URL(rme.pathname);
urlConnection = (HttpURLConnection) dirUrl.openConnection();
urlConnection.setRequestProperty("User-Agent", CoreProvider.getInstance().getUserAgent());
NonshareableBufferedDataInputStream dis = null;
String preferredFile = null;
byte[] buf = new byte[65536];
try {
if (urlConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new IOException("unable to read image directory");
}
if (!urlConnection.getContentType().startsWith("text/html")) {
throw new IOException("unable to get directory listing of images");
}
dis = new NonshareableBufferedDataInputStream(urlConnection.getInputStream(), buf);
String line;
while ((line = dis.readLine()) != null) {
int hrefPos;
if ((hrefPos = line.indexOf("href=\"")) > 0) {
int closeQuotePos = line.indexOf("\"", hrefPos + 8);
preferredFile = line.substring(hrefPos+6, closeQuotePos);
}
}
} finally {
if (dis != null) {
dis.close();
}
urlConnection.disconnect();
urlConnection = null;
}
// load the image into memory
long afterDir = System.currentTimeMillis();
URL url = new URL(rme.pathname + preferredFile);
int gzPos = preferredFile.lastIndexOf(".gz");
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestProperty("User-Agent", CoreProvider.getInstance().getUserAgent());
GZIPInputStream gzis = null;
try {
gzis = new GZIPInputStream(new NonshareableBufferedDataInputStream(urlConnection.getInputStream(), buf), 32768);
ByteSourceInputStream bsis = new ByteSourceInputStream(gzis, preferredFile.substring(0, gzPos));
TiffReader tiffReader = new TiffReader(false);
HashMap<String, Object> imgParams = new HashMap<>();
FormatCompliance formatCompliance = new FormatCompliance("reading NWS GeoTIFF");
TiffContents tiffContents = tiffReader.readContents(bsis, imgParams, formatCompliance);
long afterRead = System.currentTimeMillis();
TiffDirectory tiffDirectory = tiffContents.directories.get(0);
if (tiffDirectory.hasTiffImageData()) {
rme.img = tiffDirectory.getTiffImage();
rme.transparentColor = Color.BLACK;
rme.keepImg = true;
rme.pinpointList.clear();
int width = rme.img.getWidth();
int height = rme.img.getHeight();
// get the GeoTIFF parameters for where this image is displayed on the world
// tiepoints, [3]= UL X coordinate (deg), [4] = UL Y coordinate (deg)
double[] tiepoints = tiffDirectory.getFieldValue(GeoTiffTagConstants.EXIF_TAG_MODEL_TIEPOINT_TAG, true);
// degrees/pixel, [0] for X coord, [1] for Y coord
double[] pixelScaleXYZ = tiffDirectory.getFieldValue(GeoTiffTagConstants.EXIF_TAG_MODEL_PIXEL_SCALE_TAG, true);
double sLat = tiepoints[4] - height * pixelScaleXYZ[1]; // note we need to negate yDim
double eLon = tiepoints[3] + width * pixelScaleXYZ[0];
rme.pinpointList.add(new RasterMapEntry.PinPoint(0, 0, tiepoints[4], tiepoints[3]));
rme.pinpointList.add(new RasterMapEntry.PinPoint(width, height, sLat, eLon));
rme.isLinear = true;
rme.enabled = true;
long done = System.currentTimeMillis();
System.out.println(new Date(done).toString() + ": downloading " + preferredFile + " took " + (afterDir-startTime) + "d+" + (afterRead-afterDir)
+ "r+" + (done-afterRead) + "e msec");
} else {
throw new IOException("TIFF file doesn't contain any images");
}
// headers of interest: Last-Modified (when image was created), Expires (when next image will be created)
rme.imgCreationDate = new Date(urlConnection.getHeaderFieldDate("Last-Modified", now));
expireTime = new Date(urlConnection.getHeaderFieldDate("Expires", now + 30000));
System.out.println("at " + new Date().toString() + ", update time of NWS radar image is " + rme.imgCreationDate.toString() + ", expire=" + expireTime.toString());
} catch (ImagingException ie) {
throw new IOException("unable to read GeoTIFF image", ie);
} finally {
if (gzis != null) {
gzis.close();
}
}
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return expireTime;
}
/**
* Add the specified RasterMapEntry to the persisted list of raster map overlays.
* @param rme RasterMapEntry to add
* @return int index of newly added entry
*/
public int add(RasterMapEntry rme) {
imageList.add(rme);
if (rme.persist) {
rme.writeToPreferences(YAAC.getPreferences().node(RASTER_MAP_PREFS_NODE));
}
return imageList.size() - 1;
}
/**
* Remove the indexed record from the persisted RasterMapCatalog.
* @param index zero-based int index of the record to remove
*/
public void remove(int index) {
RasterMapEntry rme = imageList.remove(index);
if (rme.reloadTimerTask != null) {
rme.reloadTimerTask.cancel();
rme.reloadTimerTask = null;
}
YAAC.getPreferences().node(RASTER_MAP_PREFS_NODE).remove(rme.pathname);
}
/**
* Returns the data Class for the specified column.
*
* @param columnIndex the column being queried
* @return the Class object for the column's data
*/
@Override
public Class<?> getColumnClass(int columnIndex) {
return COLUMN_CLASSES[columnIndex];
}
/**
* Returns the localized name for the column.
*
* @param column the column being queried
* @return a string containing the name of <code>column</code>
*/
@Override
public String getColumnName(int column) {
return COLUMN_NAMES[column];
}
/**
* Indicate which columns can be edited.
*
* @param rowIndex the row being queried
* @param columnIndex the column being queried
* @return false
*/
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return 1 <= columnIndex;
}
/**
* Store a new value for an editable table cell.
*
* @param aValue value to assign to cell
* @param rowIndex row of cell
* @param columnIndex column of cell
*/
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
RasterMapEntry rme = imageList.get(rowIndex);
switch (columnIndex) {
case 1:
Boolean aValue1 = (Boolean) aValue;
if (aValue1.booleanValue() != rme.enabled) {
rme.enabled = aValue1;
if (rme.persist) {
rme.writeToPreferences(YAAC.getPreferences().node(RASTER_MAP_PREFS_NODE));
}
fireTableCellUpdated(rowIndex, columnIndex);
rasterMapOverlay.repaint();
}
break;
case 2:
rme.transparency = ((Number)aValue).intValue();
if (rme.persist) {
rme.writeToPreferences(YAAC.getPreferences().node(RASTER_MAP_PREFS_NODE));
}
fireTableCellUpdated(rowIndex, columnIndex);
rasterMapOverlay.repaint();
break;
case 3:
rme.persist = ((Boolean)aValue).booleanValue();
if (rme.persist) {
rme.writeToPreferences(YAAC.getPreferences().node(RASTER_MAP_PREFS_NODE));
} else {
rme.removePersistence(YAAC.getPreferences().node(RASTER_MAP_PREFS_NODE));
}
break;
default:
throw new InternalError("attempt to set value on readonly column#" + columnIndex);
}
}
/**
* Returns the number of columns in the model.
*
* @return the number of columns in the model
* @see #getRowCount
*/
public int getColumnCount() {
return COLUMN_CLASSES.length;
}
/**
* Returns the number of rows in the model. A
* <code>JTable</code> uses this method to determine how many rows it
* should display. This method should be quick, as it
* is called frequently during rendering.
*
* @return the number of rows in the model
* @see #getColumnCount
*/
public int getRowCount() {
return imageList.size();
}
/**
* Returns the value for the cell at <code>columnIndex</code> and
* <code>rowIndex</code>.
*
* @param rowIndex the row whose value is to be queried
* @param columnIndex the column whose value is to be queried
* @return the value Object at the specified cell
*/
public Object getValueAt(int rowIndex, int columnIndex) {
RasterMapEntry rme = imageList.get(rowIndex);
switch (columnIndex) {
case 0:
return rme.pathname;
case 1:
return rme.enabled;
case 2:
return rme.transparency;
case 3:
return rme.persist;
default:
throw new IllegalArgumentException("columnIndex=" + columnIndex + " not between 0 and " + (getColumnCount()-1));
}
}
/**
* Delete an entire RasterMapEntry from the catalog.
* @param rowIndex zero-based row index into the catalog table model
*/
public void deleteRow(int rowIndex) {
remove(rowIndex);
fireTableRowsDeleted(rowIndex, rowIndex);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,739 @@
package org.ka2ddo.yaac.srtm;
/*
* Copyright (C) 2011-2021 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.util.Base64;
import org.ka2ddo.yaac.YAAC;
import org.ka2ddo.yaac.core.ErrorLogger;
import org.ka2ddo.yaac.core.FullGeoMapIfc;
import org.ka2ddo.yaac.core.GuiIfc;
import org.ka2ddo.yaac.core.StatusListener;
import org.ka2ddo.yaac.core.UserAbort;
import org.ka2ddo.yaac.core.provider.CoreProvider;
import org.ka2ddo.yaac.io.NonshareableBufferedDataOutputStream;
import org.ka2ddo.yaac.io.NonshareableCountingBufferedDataInputStream;
import org.ka2ddo.util.DistanceUnit;
import java.io.EOFException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
/**
* This class manages an in-memory cache of SRTMTile objects, as well as
* providing I/O code to fetch new tiles from the USGS website.
* @author Andrew Pavlin, KA2DDO
*/
public class SRTMTileManager {
// singleton, so don't allow anyone else to create it
private SRTMTileManager() {}
private static SRTMTileManager instance = new SRTMTileManager();
/**
* Get the reference to the singleton SRTMTileManager object.
* @return SRTMTileManager instance
*/
public static SRTMTileManager getInstance() {
return instance;
}
/**
* Maximum size of tile cache in tiles (to keep memory usage under control).
*/
private static final int MAX_TILES = 64;
private final LinkedHashMap<SRTMTileId, SRTMTile> cache = new LinkedHashMap<SRTMTileId, SRTMTile>(MAX_TILES, 1.0F, true);
private final File[] latitudeDirectoryCache = new File[180];
private final File[][] longitudeFileCache = new File[180][];
private File tileRootDirectory;
/**
* Get a SRTMTile object for the 1-degree by 1-degree region of the planet requested.
* @param latitude int latitude in millionths of degrees North
* @param longitude int longitude in millionths of degrees East
* @return SRTMTile object containing elevation data for the specified latitude/longitude
*/
public SRTMTile getTile(int latitude, int longitude) {
return getTile(latitude * 0.000001, longitude * 0.000001);
}
/**
* Get a SRTMTile object for the 1-degree by 1-degree region of the planet requested.
* @param latitude double latitude in fractional degrees North
* @param longitude double longitude in fractional degrees East
* @return SRTMTile object containing elevation data for the specified latitude/longitude
*/
public SRTMTile getTile(double latitude, double longitude) {
int iLat = (int) Math.floor(latitude);
int iLon = (int) Math.floor(longitude);
SRTMTileId id = new SRTMTileId(iLat, iLon);
SRTMTile tile;
if ((tile = cache.get(id)) == null) {
if (cache.size() > MAX_TILES || getEstimatedCacheSize() > Runtime.getRuntime().maxMemory() / 10) {
Iterator<SRTMTile> it = cache.values().iterator();
it.next();
it.remove();
}
// need to load tile from file
if (tileRootDirectory == null) {
tileRootDirectory = new File(YAAC.getProps().getProperty(YAAC.OPT_OSM_TILE_DIR));
}
File zTileFile = getLongitudeFile(iLat, iLon, ".zip");
if (zTileFile.exists()) {
tile = new SRTMTile();
try {
tile.loadZip(zTileFile, id.latitude, id.longitude);
} catch (IOException e) {
e.printStackTrace(System.out);
}
cache.put(id, tile);
} else {
zTileFile = getLongitudeFile(iLat, iLon, ".gz");
if (zTileFile.exists()) {
tile = new SRTMTile();
try {
tile.loadGzip(zTileFile, id.latitude, id.longitude);
} catch (IOException e) {
e.printStackTrace(System.out);
}
cache.put(id, tile);
} else {
// remember an empty tile
cache.put(id, tile = new SRTMTile());
tile.setCoord(id);
}
}
if (cache.size() > MAX_TILES || getEstimatedCacheSize() > Runtime.getRuntime().maxMemory() / 10) {
Iterator<SRTMTile> it = cache.values().iterator();
it.next();
it.remove();
}
}
return tile;
}
private File getLatitudeDirectory(int latitude) {
int dirIdx = Math.min(latitude + 90, latitudeDirectoryCache.length - 1);
if (latitudeDirectoryCache[dirIdx] == null) {
if (latitude < 0) {
latitudeDirectoryCache[dirIdx] = new File(tileRootDirectory, "S" + (-latitude));
} else {
latitudeDirectoryCache[dirIdx] = new File(tileRootDirectory, "N" + latitude);
}
}
return latitudeDirectoryCache[dirIdx];
}
File getLongitudeFile(int latitude, int longitude, String suffix) {
int latIdx = Math.min(latitude + 90, 180);
int lonIdx = Math.min(longitude + 180, 359);
File[][] longitudeFileCache1 = longitudeFileCache; // avoid getfield opcode
if (longitudeFileCache1[latIdx] == null) {
longitudeFileCache1[latIdx] = new File[360];
}
if (longitudeFileCache1[latIdx][lonIdx] == null ||
!longitudeFileCache1[latIdx][lonIdx].getPath().endsWith(suffix)) {
if (longitude < 0) {
longitudeFileCache1[latIdx][lonIdx] = new File(getLatitudeDirectory(latitude), "W" + (-longitude) + ".hgt" + suffix);
} else {
longitudeFileCache1[latIdx][lonIdx] = new File(getLatitudeDirectory(latitude), "E" + longitude + ".hgt" + suffix);
}
}
return longitudeFileCache1[latIdx][lonIdx];
}
/**
* Get the SRTM elevation at the specified lat/lon coordinates.
* @param latitude double latitude in fractional degrees North
* @param longitude double longitude in fractional degrees East
* @return elevation at the tile pixel for the specified coordinates,
* or NO_CELL if there is no tile for the specified coordinates
*/
public short getElevation(double latitude, double longitude) {
SRTMTile tile = getTile(latitude, longitude);
if (tile.zCells == null) {
return SRTMTile.NO_CELL;
}
int ix = (int)((longitude - tile.minLon) * (tile.numCellsPerAxis - 1) + 0.5);
int iy = (int)((latitude - tile.minLat) * (tile.numCellsPerAxis - 1) + 0.5);
return tile.zCells[iy][ix];
}
/**
* Clear cached pointers to tile files. This is called when the configuration dialog
* changes the tile root directory path.
*/
public void flushFileCache() {
tileRootDirectory = null;
for (int i = 0; i < latitudeDirectoryCache.length; i++) {
latitudeDirectoryCache[i] = null;
File[] lonFile = longitudeFileCache[i];
if (lonFile != null) {
for (int j = 0; j < lonFile.length; j++) {
lonFile[j] = null;
}
longitudeFileCache[i] = null;
}
}
cache.clear();
}
/**
* Get an estimate of the amount of memory used by the SRTM tile cache.
* @return estimated byte count used by tiles in memory
*/
public int getEstimatedCacheSize() {
/* note this only counts up the size of the tiles, because they are by far the largest
components of this cache. However, the latitudeDirectoryCache and longitudeDirectoryCache
can also consume a visible portion of memory.
*/
int totalSize = 0;
for (SRTMTile tile : cache.values()) {
totalSize += tile.getSize() + 32; // 28 is estimated overhead for File object in longitudeFileCache, 4 is estimate for fraction of higher arrays' overhead
}
return totalSize;
}
/**
* Download NASADEM tiles from the US Geological Survey's LP DAAC website to cover a specified region
* of the planet. Note these are exactly the same format as SRTM1 tiles from the former unsecured USGS website.
* @param radius radius to cover in kilometers
* @param lat center latitude of region to download in fractional degrees North
* @param lon center longitude of region to download in fractional degrees East
* @param hiRes boolean true if 1-arc-second tiles should be downloaded where available (currently ignored)
*/
public static void loadPrecompiledTiles(double radius, double lat, double lon, final boolean hiRes) {
final StatusListener pd = YAAC.getGui().createProgressReporter(YAAC.getMsg("menu.File.Topo.DownloadTiles.downloading"), false);
double deltaLat = DistanceUnit.DEG_LAT.from(DistanceUnit.KM) * radius;
final int minLat = (int)Math.max(Math.floor(lat - deltaLat), -90.0);
final int maxLat = (int)Math.min(Math.ceil(lat + deltaLat), 90.0);
final int minLon = (int)Math.max(Math.floor(lon - deltaLat), -180.0);
final int maxLon = (int)Math.min(Math.ceil(lon + deltaLat), 180.0);
Thread t = new Thread(new Runnable() {
public void run() {
try {
String rootTilePath = YAAC.getProps().getProperty(YAAC.OPT_OSM_TILE_DIR);
File tileDir = new File(rootTilePath);
double tileStep = 100.0 / ((maxLat - minLat) * (maxLon - minLon));
double progress = 0.0;
long[] numBytesTransferred = new long[1];
long[] totalTransferTime = new long[1];
totalTransferTime[0] = 1; // to avoid divide-by-zero errors
String[] authentication = new String[1];
HashMap<String, Cookie> cookieMap = new HashMap<>();
for (int bLat = minLat; bLat < maxLat; bLat++) {
for (int bLon = minLon; bLon < maxLon; bLon++) {
try {
// try to download height file
if (!downloadTileFile("e4ftl01.cr.usgs.gov", tileDir, bLat, bLon, hiRes, pd, numBytesTransferred, totalTransferTime, authentication, cookieMap)) {
//TODO: what's the backup now?
}
} catch (IOException e1) {
e1.printStackTrace(System.out);
}
pd.updateProgress(progress);
progress += tileStep;
}
}
} catch (UserAbort userAbort) {
ErrorLogger.reportError(pd, YAAC.getMsg("menu.File.Topo.DownloadTiles.abort"));
} catch (Throwable e) {
ErrorLogger.reportError(pd, e, YAAC.getMsg("menu.File.Topo.DownloadTiles.downloadException"));
}
instance.flushFileCache();
YAAC.getGui().iterateOverAllMaps(new GuiIfc.FullMapFunctor() {
public void applyToMap(FullGeoMapIfc map) {
map.regenerateTopo();
}
});
pd.setOperationComplete();
}
}, "SRTM Tile Copier");
t.setDaemon(true);
t.start();
}
private static class Cookie {
final String cookieName;
final String cookieValue;
String domain;
boolean supportSubDomains;
String path;
long expires;
boolean secure;
Cookie(String name, String value, String host, String path) {
this.cookieName = name;
this.cookieValue = value;
this.domain = host.toLowerCase(Locale.ENGLISH);
this.path = path;
}
boolean isURLCompatible(URL url, long now) {
return (now < expires || expires == 0L) &&
(!secure || url.getProtocol().equalsIgnoreCase("https")) &&
(supportSubDomains ? url.getHost().toLowerCase(Locale.ENGLISH).endsWith(domain) : url.getHost().equalsIgnoreCase(domain)) &&
url.getPath().startsWith(path);
}
boolean isExpired(long now) {
return !(now < expires || expires == 0L);
}
@Override
public String toString() {
return "[" + cookieName + '=' + cookieValue + "; Domain=" + domain + "; Path=" +
path + (expires > 0L ? "; Expires=" + new Date(expires) : "") +
(secure ? "; Secure" : "") + ']';
}
}
/**
* Download one tile file (ways or nodes) from a specific tile directory.
* @param hostIpAddress String of numeric IP address of webserver (to avoid repeated lookups)
* @param tileDir root directory of the tile hierarchy
* @param bLat latitude of tile (degrees only)
* @param bLon longitude of tile (degrees only)
* @param hiRes if boolean true, try to get 1-arc-second tiles when available
* @param pd StatusListener to receive progress reports
* @param numBytesTransferred OUT parameter to accumulate the number of data bytes transferred
* @param totalTransferTime OUT parameter to accumulate the time spent transferring data (in milliseconds) @throws IOException if data cannot be transferred for some reason
* @param authentication 1-element String array caching the HTTP authentication
* information (so the user doesn't have to be prompted more
* than once for their authentication information); can be null
* if there is no context to query a human user for authentication info
* @param cookieMap HashMap of cookie names to values for any cookies needed by
* the USGS website's authentication mechanism
*/
static boolean downloadTileFile(String hostIpAddress,
File tileDir,
int bLat,
int bLon,
boolean hiRes,
StatusListener pd,
long[] numBytesTransferred,
long[] totalTransferTime,
String[] authentication,
HashMap<String, Cookie> cookieMap) throws IOException {
if (CoreProvider.getInstance().isNonencryptedHTTPForced()) {
throw new IOException(YAAC.getMsg("HttpEncryptionProhibited"));
}
String inputFileName = String.format("%s%02d%s%03d", (bLat < 0 ? "s" : "n"), Math.abs(bLat), (bLon < 0 ? "w" :"e"), Math.abs(bLon)) + ".zip";
pd.updateMessage(inputFileName + " (" + ((float)numBytesTransferred[0] / totalTransferTime[0]) + "Kb/sec)");
int numOverloadTimes = 0;
boolean notFound = true;
File latDir = new File(tileDir, bLat < 0 ? "S" + (-bLat) : "N" + bLat);
File lonFile = new File(latDir, (bLon < 0 ? "W" + (-bLon) : "E" + bLon) + ".hgt.zip");
if (hiRes || !lonFile.exists()) {
File oldLonFile = new File(latDir, (bLon < 0 ? "W" + (-bLon) : "E" + bLon) + ".hgt.zip");
if (oldLonFile.exists()) {
oldLonFile.delete();
}
URL serverURL = new URL("https", hostIpAddress, "/MEASURES/NASADEM_HGT.001/2000.02.11/NASADEM_HGT_" + inputFileName);
do {
long startTime = System.currentTimeMillis();
long endTime;
HttpURLConnection conn = (HttpURLConnection) serverURL.openConnection();
conn.setInstanceFollowRedirects(false);
try {
conn.setRequestProperty("User-Agent", CoreProvider.getInstance().getUserAgent());
if (authentication != null) {
if (authentication[0] != null) {
conn.setRequestProperty("Authorization", authentication[0]);
}
}
if (cookieMap.size() > 0) {
// build up a string of cookies to send, based on current context
StringBuilder b = new StringBuilder();
long now = System.currentTimeMillis();
for (Iterator<Cookie> itCookie = cookieMap.values().iterator(); itCookie.hasNext(); ) {
Cookie cookie = itCookie.next();
if (cookie.isURLCompatible(serverURL, now)) {
if (b.length() > 0) {
b.append("; ");
}
b.append(cookie.cookieName).append('=').append(cookie.cookieValue);
} else if (cookie.isExpired(now)) {
itCookie.remove();
}
}
if (b.length() > 0) { // found some usable cookies?
conn.setRequestProperty("Cookie", b.toString());
}
}
int responseCode = conn.getResponseCode();
String responseMsg;
if ((responseMsg = conn.getResponseMessage()) == null) {
responseMsg = "";
}
{
Map<String, List<String>> headerFields = conn.getHeaderFields();
List<String> cookies = headerFields.get("Set-Cookie");
if (cookies != null && cookies.size() > 0) {
SimpleDateFormat sdfExpires = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
String host = serverURL.getHost().toLowerCase(Locale.ENGLISH);
String path = serverURL.getPath();
onecookieloop:
for (String cookieStr : cookies) {
StringTokenizer tk = new StringTokenizer(cookieStr, ";");
String cookieKV = tk.nextToken().trim();
int delim = cookieKV.indexOf('=');
Cookie cookie = new Cookie(cookieKV.substring(0, delim), cookieKV.substring(delim+1), host, path);
while (tk.hasMoreTokens()) {
String param = tk.nextToken().trim();
delim = param.indexOf('=');
String paramKey;
String paramVal;
if (delim > 0) {
paramKey = param.substring(0, delim);
paramVal = param.substring(delim+1);
} else {
paramKey = param;
paramVal = null;
}
if (paramKey.equalsIgnoreCase("Expires")) {
try {
cookie.expires = sdfExpires.parse(paramVal).getTime();
} catch (Exception e) {
e.printStackTrace(System.out);
continue onecookieloop; // this cookie is bad, so discard it
}
} else if (paramKey.equalsIgnoreCase("Max-Age")) {
try {
cookie.expires = System.currentTimeMillis() + Long.parseLong(paramVal) * 1000L;
} catch (NumberFormatException e) {
e.printStackTrace(System.out);
continue onecookieloop; // this cookie is bad, so discard it
}
} else if (paramKey.equalsIgnoreCase("Domain")) {
String domain = paramVal.toLowerCase(Locale.ENGLISH);
if (domain.contains(".")) { // must be at least second-level
if (!domain.startsWith(".")) {
if (host.equals(domain) || host.endsWith("." + domain)) {
cookie.domain = domain;
cookie.supportSubDomains = true;
} else {
// bad cookie domain, so throw away the whole cookie
System.out.println("bad domain \"" + domain + "\" for " + cookie);
continue onecookieloop;
}
} else {
if (host.endsWith(domain)) {
cookie.domain = domain;
cookie.supportSubDomains = true;
} else {
// bad cookie domain, so throw away the whole cookie
System.out.println("bad domain \"" + domain + "\" for " + cookie);
continue onecookieloop;
}
}
} else {
// bad cookie domain, so throw away the whole cookie
System.out.println("bad domain \"" + domain + "\" for " + cookie);
continue onecookieloop;
}
} else if (paramKey.equalsIgnoreCase("Path")) {
cookie.path = paramVal;
} else if (paramKey.equalsIgnoreCase("Secure")) {
cookie.secure = true;
// don't need to check for HttpOnly since this code doesn't execute webpage scripts
}
}
cookieMap.put(cookie.cookieName, cookie);
}
}
}
if (responseCode == HttpURLConnection.HTTP_OK) {
if (!latDir.exists()) {
if (!latDir.mkdirs()) {
throw new IOException("unable to create tile latitude subdirectory " + latDir.getAbsolutePath());
}
}
NonshareableCountingBufferedDataInputStream cin = null;
NonshareableBufferedDataOutputStream bos = null;
long expectedSize = -1L;
String sizeStr = conn.getRequestProperty("Content-Length");
if (sizeStr != null && sizeStr.trim().length() > 0) {
expectedSize = Long.parseLong(sizeStr.trim());
}
try {
try {
cin = new NonshareableCountingBufferedDataInputStream(conn.getInputStream(), 16384);
bos = new NonshareableBufferedDataOutputStream(new FileOutputStream(lonFile, false), 16384);
int b;
while ((b = cin.read()) > -1) {
bos.write(b);
}
} catch (EOFException e2) {
// do nothing
} finally {
endTime = System.currentTimeMillis();
if (cin != null) {
cin.close();
}
}
} finally {
if (bos != null) {
bos.close();
}
}
if (cin != null) {
long actualSize = cin.getByteCount();
// check if we got everything
if (expectedSize > 0 && actualSize != expectedSize) {
System.out.println("error loading SRTMTile " + inputFileName + ", expected " + expectedSize + ", got " + actualSize);
}
numBytesTransferred[0] += actualSize;
totalTransferTime[0] += endTime - startTime;
try {
// stall to keep from overloading USGS webserver
Thread.sleep(1500L);
} catch (InterruptedException e) {
// ignore premature wakeup
}
notFound = false;
}
break;
} else if (HttpURLConnection.HTTP_UNAUTHORIZED == responseCode) {
// the new NASA/USGS server requires authentication
if (authentication != null) {
//TODO: do we really need to look for multiple headers of this type to find one we understand?
String authReq = conn.getHeaderField("WWW-Authenticate");
if (authReq != null && authReq.length() > 0) {
String scheme = null;
HashMap<String, String> paramMap = new HashMap<>();
int pos1 = 0;
int pos2sp, pos2c, pos2q, pos2eq;
authReq = authReq.trim();
while (pos1 < authReq.length()) {
pos2sp = authReq.indexOf(' ', pos1);
pos2c = authReq.indexOf(',', pos1);
pos2eq = authReq.indexOf('=', pos1);
if (pos2sp > 0) {
if (pos2eq < pos2sp) {
// parameter of some sort, may be quoted
String paramKey = authReq.substring(pos1, pos2eq);
pos1 = pos2eq + 1; // past the equals sign
String paramVal;
if (authReq.charAt(pos1) == '"') { // quoted?
// scan until close quote
pos1++;
pos2q = pos1;
while ((pos2q = authReq.indexOf('"', pos2q + 1)) > 0) {
if (authReq.charAt(pos2q - 1) != '\\') {
break; // wasn't an escaped quote
}
}
paramVal = authReq.substring(pos1, pos2q).replace("\\", "");
pos1 = pos2q + 1;
if (pos1 < authReq.length() && authReq.charAt(pos1) == ',') {
pos1++;
}
} else {
if (pos2c > pos1) {
paramVal = authReq.substring(pos1, pos2c);
pos1 = pos2c + 1;
} else {
paramVal = authReq.substring(pos1);
pos1 = authReq.length(); // end of line
}
}
// skip any spaces before the next token
while (pos1 < authReq.length() && authReq.charAt(pos1) == ' ') {
pos1++;
}
paramMap.put(paramKey.toLowerCase(Locale.ENGLISH), paramVal);
} else if (pos2c > 0 && pos2c < pos2sp) {
// parameter without value
paramMap.put(authReq.substring(pos1, pos2c).toLowerCase(Locale.ENGLISH), "");
pos1 = pos2c + 1;
// skip any spaces before the next token
while (pos1 < authReq.length() && authReq.charAt(pos1) == ' ') {
pos1++;
}
} else {
// did we have a previous scheme?
if (scheme != null) {
//TODO: test if we support it
if (buildAuthenticationToken(scheme, paramMap, authentication)) {
scheme = null; // mark so we don't do it again
break; // this challenge is good enough, use it
}
}
// must be a scheme name
scheme = authReq.substring(pos1, pos2sp);
if (scheme.startsWith("\"")) {
if (scheme.endsWith("\"")) {
scheme = scheme.substring(1, scheme.length() - 1);
} else {
//TODO: we need to extract more text until we end the quoted and space delimited scheme name
throw new UnsupportedOperationException("don't support authentication schemes with spaces in scheme name");
}
}
paramMap.clear();
pos1 = pos2sp + 1;
// skip any spaces before the next token
while (pos1 < authReq.length() && authReq.charAt(pos1) == ' ') {
pos1++;
}
}
}
}
if (scheme != null) {
// last scheme, so let's try it
if (!buildAuthenticationToken(scheme, paramMap, authentication)) {
ErrorLogger.reportError(pd, MessageFormat.format(YAAC.getMsg("menu.File.Topo.DownloadTiles.downloadError"),
responseCode, inputFileName, responseMsg));
break; // we don't know how to authenticate for this website
}
}
continue;
}
} else {
// no source to ask user for authentication credentials, so just quit
throw new IOException("unable to ask user to authenticate access to website");
}
} else if (HttpURLConnection.HTTP_MOVED_PERM == responseCode ||
HttpURLConnection.HTTP_MOVED_TEMP == responseCode) {
String newUrl = conn.getHeaderField("Location");
System.out.println(inputFileName + " moved to " + newUrl);
int startOfHost = newUrl.indexOf("://") + 3;
if (startOfHost < 2) {
// no scheme or host prefix, what to do?
throw new IOException("redirect with incomplete URL: " + newUrl);
}
int endOfHost = newUrl.indexOf('/', startOfHost + 1);
int altEndOfHost = newUrl.indexOf(':', startOfHost + 1);
if (altEndOfHost > startOfHost && altEndOfHost < endOfHost) {
endOfHost = altEndOfHost;
}
boolean looksLikeHostName = false;
for (int i = startOfHost; i < endOfHost; i++) {
if (Character.isLetter(newUrl.charAt(i))) {
looksLikeHostName = true;
break;
}
}
if (!looksLikeHostName) {
// try swapping in the original hostname
newUrl = newUrl.substring(0, startOfHost) + "dds.cr.usgs.gov" + newUrl.substring(endOfHost);
System.out.println(" changed URL to " + newUrl);
}
serverURL = new URL(newUrl);
continue;
} else if (HttpURLConnection.HTTP_NOT_FOUND == responseCode) {
try {
// stall to keep from overloading USGS webserver
Thread.sleep(1500L);
} catch (InterruptedException e) {
// ignore premature wakeup
}
} else if (HttpURLConnection.HTTP_UNAVAILABLE == responseCode) {
try {
// stall to keep from overloading USGS webserver (they must be too busy)
Thread.sleep(1500L * (1 + numOverloadTimes));
} catch (InterruptedException e) {
// ignore premature wakeup
}
if (numOverloadTimes > 0) {
ErrorLogger.reportError(pd, MessageFormat.format(YAAC.getMsg("menu.File.Topo.DownloadTiles.downloadError"),
responseCode, inputFileName, responseMsg));
}
numOverloadTimes++;
notFound = false;
} else {
ErrorLogger.reportError(pd, MessageFormat.format(YAAC.getMsg("menu.File.Topo.DownloadTiles.downloadError"),
responseCode, inputFileName, responseMsg));
}
} catch (IOException e) {
e.printStackTrace(System.out);
ErrorLogger.reportError(YAAC.getGui(), e, YAAC.getMsg("menu.File.Topo.DownloadTiles.downloadException"));
break;
} finally {
if (conn != null) {
conn.disconnect();
}
}
break;
} while (true);
}
return !notFound;
}
private static boolean buildAuthenticationToken(String scheme, HashMap<String,String> paramMap, String[] authentication) {
if (scheme.equalsIgnoreCase("Basic")) {
GuiIfc gui;
if ((gui = YAAC.getGui()) != null) {
// get parameters from challenge
String charset = paramMap.get("charset");
if (charset == null) {
charset = "UTF-8";
}
String realm = paramMap.get("realm");
if (realm == null) {
realm = "";
}
String title = paramMap.get("title");
if (title == null) {
title = realm;
}
// ask the user for the username
String username = gui.showGenericInputDialog("AuthenticationDialog.defaultTitle",
MessageFormat.format(YAAC.getMsg("AuthenticationDialog.enterUsername"),
title, realm));
if (username == null) {
return false; // aborted
}
// ask the user for the password
String password = gui.showGenericPasswordDialog("AuthenticationDialog.defaultTitle",
MessageFormat.format(YAAC.getMsg("AuthenticationDialog.enterPassword"),
title));
if (password == null) {
return false; // aborted
}
// build the response
try {
authentication[0] = "Basic " + Base64.byteArrayToBase64((username + ':' + password).getBytes(charset));
return true;
} catch (UnsupportedEncodingException e) {
e.printStackTrace(System.out);
}
}
}
return false;
}
}

View File

@ -0,0 +1,200 @@
package org.ka2ddo.yaac.legacyrxtx.io;
/*
* Copyright (C) 2011-2019 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import gnu.io.RXTXPort;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import java.io.IOException;
import java.io.InputStream;
import java.util.TooManyListenersException;
/**
* This class is a wrapper around a RXTX SerialPort's SerialInputStream,
* so that the SerialPort can be closed while a blocking read is outstanding
* and properly abort the read.
*/
public class CloseableWhileReadingSerialInputStream extends InputStream implements SerialPortEventListener {
private transient InputStream serialInputStream;
private static final int BUFSIZE = 512;
private final byte[] buf = new byte[BUFSIZE];
/**
* The last position in buf written by received byte.
*/
private transient int inPos = -1;
/**
* The last position to be passed on to the reader using this stream.
*/
private transient int outPos = -1;
/**
* Create an InputStream that can be closed even if there are outstanding reads on a flow-control-blocked
* or disconnected USB serial device.
* @param serialPort SerialPort object identifying the port
* @throws IOException if the stream from the port could not be opened
* @throws TooManyListenersException if SerialPort is already in use by some other code
*/
public CloseableWhileReadingSerialInputStream(SerialPort serialPort) throws IOException, TooManyListenersException {
serialInputStream = serialPort.getInputStream();
serialPort.addEventListener(this);
serialPort.notifyOnDataAvailable(true);
}
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @throws java.io.IOException if an I/O error occurs.
*/
public int read() throws IOException {
while (true) {
synchronized (this) {
if (inPos >= 0 && inPos != outPos) {
++outPos;
if (outPos >= BUFSIZE) {
outPos = 0;
}
int b = buf[outPos] & 0xFF;
// in case this thread let a backlog build up
notifyAll();
return b;
}
}
if (serialInputStream == null) {
return -1; // file closed
}
synchronized (this) {
try {
wait(500L);
} catch (InterruptedException e) {
// do nothing
}
}
}
}
/**
* Closes this input stream and releases any system resources associated
* with the stream.
*
* @throws java.io.IOException if an I/O error occurs.
*/
@Override
public synchronized void close() throws IOException {
synchronized (RXTXPort.class) {
if (serialInputStream != null) {
serialInputStream.close();
serialInputStream = null;
}
}
notifyAll();
}
/**
* Returns an estimate of the number of bytes that can be read (or
* skipped over) from this input stream without blocking by the next
* invocation of a method for this input stream. The next invocation
* might be the same thread or another thread. A single read or skip of this
* many bytes will not block, but may read or skip fewer bytes.
*
* @return an estimate of the number of bytes that can be read (or skipped
* over) from this input stream without blocking or {@code 0} when
* it reaches the end of the input stream.
* @throws java.io.IOException if an I/O error occurs.
*/
@Override
public int available() throws IOException {
if (serialInputStream == null) {
throw new IOException("stream closed");
}
int avail = inPos - outPos;
if (avail < 0) {
avail = BUFSIZE + avail;
}
return avail;
}
/**
* Tests if this input stream supports the <code>mark</code> and
* <code>reset</code> methods. Whether or not <code>mark</code> and
* <code>reset</code> are supported is an invariant property of a
* particular input stream instance. The <code>markSupported</code> method
* returns <code>false</code>.
*
* @return <code>true</code> if this stream instance supports the mark
* and reset methods; <code>false</code> otherwise.
*/
@Override
public boolean markSupported() {
return false;
}
/**
* DO NOT CALL. Callback listener method for asynchronous notification of input characters
* being available.
* @param ev SerialPortEvent describing the asynchronous event
*/
public void serialEvent(SerialPortEvent ev) {
switch (ev.getEventType()) {
case SerialPortEvent.DATA_AVAILABLE:
while (!(-1 == inPos || (inPos + 1) % BUFSIZE != outPos)) {
synchronized (this) {
try {
wait(500L);
} catch (InterruptedException e) {
// ignore this
}
}
}
if (serialInputStream != null) {
try {
int read = serialInputStream.read();
synchronized (this) {
if (-1 == read) {
inPos = -1;
outPos = -1;
serialInputStream.close();
serialInputStream = null;
} else {
++inPos;
if (inPos >= BUFSIZE) {
inPos = 0;
}
buf[inPos] = (byte) read;
}
notifyAll();
}
} catch (IOException e) {
e.printStackTrace();
}
}
break;
default: // we don't care about other event types
break;
}
}
}

View File

@ -0,0 +1,420 @@
package org.ka2ddo.yaac.io;
/*
* Copyright (C) 2011-2021 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import jssc.SerialPort;
import jssc.SerialPortEvent;
import jssc.SerialPortEventListener;
import org.ka2ddo.ax25.AX25Callsign;
import org.ka2ddo.ax25.AX25Frame;
import org.ka2ddo.util.ReschedulableTimer;
import org.ka2ddo.util.ReschedulableTimerTask;
import org.ka2ddo.yaac.core.HelpTaggedIOException;
import org.ka2ddo.yaac.core.StringLogger;
import org.ka2ddo.yaac.util.Localizer;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
/**
* This class implements a receive-only port for listening to the non-standard command-mode
* style APRS packet output of the Yaesu FTM-400DR and related radios, and to Kantronics
* TNCs in command mode.
* <p>The expected packet format is in two lines. Line 1 contains the AX.25 frame header
* information in the format:</p>
* <code><i>src</i>&gt;<i>dest</i>,<i>digi</i> [<i>MM</i>/<i>dd</i>/<i>yy</i> <i>HH</i>:<i>mm</i>:<i>ss</i>] &lt;UI&gt;:</code>
* <p>where the ",<i>digi</i>" part is optional (if the packet wasn't specified for digipeating). The
* Kantronics variation is the format:</p>
* <code><i>src</i>&gt;<i>dest</i>,<i>digi</i>: &lt;UI&gt;:</code>
* <p>where no date is provided, and an extra colon character is immediately
* after the last digipeater (much like the APRS-IS/TNC2 format, <b>if</b> there
* wasn't an encoding of the frame type and a line break stuck in between the header and
* the body).</p>
* <p>The second line is the body of the packet in plain text (should be ASCII, but this class
* supports UTF-8 just in case). For APRS packets, this should just be a printable packet.</p>
* @author Andrew Pavlin, KA2DDO
*/
public class YaesuConnector extends TNCConnector implements SerialPortEventListener {
/**
* The PortConnector type name for YaesuConnector.
*/
public static final String TYPE_NAME = "Yaesu";
/**
* The fully-qualified platform-wildcarded class name for the configuration editor for YaesuConnector.
*/
public static final String CONFIG_GUI = "org.ka2ddo.yaac.*.io.YaesuPortEditor";
private static final EnumSet<PortConfig.Fields> x_BLANK_FIELDS = EnumSet.noneOf(PortConfig.Fields.class);
/**
* Set of PortConfig field identifiers for fields that should not be copied from another system and should therefore be
* blanked out when copying configuration files.
*/
public static final Set<PortConfig.Fields> BLANK_FIELDS = Collections.unmodifiableSet(x_BLANK_FIELDS);
private static final IdentityHashMap<PortConfig.Fields, PortConfig.RequireHints> x_REQ_FIELDS = new IdentityHashMap<PortConfig.Fields, PortConfig.RequireHints>();
/**
* List of PortConfig field identifiers that are mandated for this port type to work, and the ResourceBundle key to
* prompt for a value if missing.
*/
public static final Map<PortConfig.Fields, PortConfig.RequireHints> REQ_FIELDS = Collections.unmodifiableMap(x_REQ_FIELDS);
static {
x_BLANK_FIELDS.add(PortConfig.Fields.deviceName);
x_REQ_FIELDS.put(PortConfig.Fields.deviceName, new PortConfig.RequireHints("ConfigImporter.serialPort", PortConfig.HintType.SERIAL_PORTS));
x_BLANK_FIELDS.add(PortConfig.Fields.callsign);
x_REQ_FIELDS.put(PortConfig.Fields.callsign, new PortConfig.RequireHints("ConfigImporter.callsign", PortConfig.HintType.CALLSIGN_SSID));
}
private SerialPort serialPort;
private transient String cachedToString = null;
private byte[] rcvBuf = new byte[1024];
private int wPtr = 0;
transient AX25Frame rcvdFrame = null;
private SimpleDateFormat sdfTimestamp;
private transient StringLogger logOut = null;
private static ReschedulableTimer yaesuTimer = new ReschedulableTimer("Yaesu packet flush timer");
private ReschedulableTimerTask yaesuTimerTask = new ReschedulableTimerTask() {
@Override
public void run() {
if (rcvdFrame != null) {
// pass built frame to AX25Stack for further analysis
flushFrame();
fireReceiving(false);
}
}
};
/**
* Set the correct defaults for a port in process of being created.
* @param cfg PortConfig.Cfg whose defaults should be adjusted
*/
public static void fillinConfigDefaults(PortConfig.Cfg cfg) {
if (cfg.baudRate == 0) {
cfg.baudRate = 9600;
cfg.passcode = "yy/dd/MM"; // presumably, the Yaesu factory default
}
}
/**
* Update the configuration of the connector to match the updated
* setup.
*
* @param portConfig PortConfig defining new port settings
* @throws java.io.IOException if interface changes could not be applied
* @throws IllegalArgumentException if type information is invalid for
* changing the settings of this PortConnector
*/
@Override
public void configure(PortConfig portConfig) throws IOException, IllegalArgumentException {
if (!TYPE_NAME.equals(portConfig.portType)) {
throw new IllegalArgumentException("invalid portType=" + portConfig.portType);
}
if (serialPort != null) {
this.close();
}
setPortConfig(portConfig);
try {
cachedToString = null;
if (portConfig.enabled) {
if (serialPort == null) {
serialPort = new SerialPort(currentCfg.deviceName);
serialPort.openPort();
serialPort.addEventListener(this, SerialPort.MASK_RXCHAR | SerialPort.MASK_ERR);
}
serialPort.setParams(currentCfg.baudRate,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
serialPort.setDTR(true);
serialPort.setRTS(true);
if (currentCfg.passcode == null || currentCfg.passcode.length() == 0) {
currentCfg.passcode = "yy/dd/MM"; // presumed factory default
}
sdfTimestamp = new SimpleDateFormat(currentCfg.passcode);
if (currentCfg.callsign.length() > 0) {
setPortAddress(new AX25Callsign(currentCfg.callsign));
}
if (currentCfg.flowControlled) {
logOut = new StringLogger("Yaesu-", "yyyyMMMdd-HH", ".txt");
logOut.setFlushInterval(300);
}
PortManager.firePortOpened(this);
}
} catch (Exception e) {
throw new HelpTaggedIOException(MessageFormat.format(Localizer.getMsg("dialog.IOError.unableToOpen"), TYPE_NAME, currentCfg.deviceName), e, "FAQ.oldTNC");
}
}
/**
* Reports whether this PortConnector has an open connection to its port.
*
* @return boolean true if PortConnector is open
*/
public boolean isOpen() {
return serialPort != null;
}
/**
* Shut down this port connection. Expected to be overridden by sub-classes.
*/
@Override
public void close() {
if (serialPort != null) {
try {
serialPort.closePort();
serialPort = null;
} catch (Exception e) {
System.out.print("Error closing Yaesu input stream: ");
e.printStackTrace(System.out);
}
}
cachedToString = null;
PortManager.firePortClosed(this);
fireReceiving(false);
if (logOut != null) {
logOut.shutdown();
logOut = null;
}
}
/**
* Specify what capabilities a port of this type has.
*
* @return bitmask of capability flags
* @see #CAP_RCV_PACKET_DATA
* @see #CAP_XMT_PACKET_DATA
* @see #CAP_FULL_DUPLEX
* @see #CAP_GPS_DATA
* @see #CAP_HF
* @see #CAP_IGATE
* @see #CAP_OPENTRAC
* @see #CAP_RF
* @see #CAP_WAYPOINT_SENDER
* @see #CAP_WEATHER
*/
@Override
public int getCapabilities() {
return CAP_RCV_PACKET_DATA | CAP_RF;
}
/**
* Process incoming serial port event.
* @param event SerialPortEvent describing the handling needed
*/
public void serialEvent(SerialPortEvent event) {
if (event.getEventType() == SerialPortEvent.RXCHAR) {
int newData;
try {
while (serialPort != null && serialPort.getInputBufferBytesCount() > 0) {
newData = serialPort.readBytes(1)[0] & 0xFF;
switch (newData) {
case '\n':
if (wPtr == 0) {
break;
}
// must be a radio that does Unix EOLs instead of Microsoft
case '\r':
String line = new String(rcvBuf, 0, wPtr, StandardCharsets.UTF_8);
if (logOut != null) {
logOut.log(line);
}
// is this the first line or second+ line of the Yaesu report?
if (rcvdFrame == null || looksLikeFirstLine(line)) {
try {
if (rcvdFrame != null) {
flushFrame();
}
// line format expected to be src>dest,digi,... [MM/dd/yy HH:mm:ss] <pkttype>:
// reject line if format not correct
int dstPos = line.indexOf('>');
int kamEndOfDigiPos = line.indexOf(':', dstPos + 3);
int datePos = line.indexOf('[', dstPos + 3);
int endOfDigiPos = datePos > 0 ? datePos - 1 : kamEndOfDigiPos;
int pidPos = line.indexOf('<', datePos + 17);
// we think we can decode this
AX25Frame f = new AX25Frame();
f.sender = new AX25Callsign(line.substring(0, dstPos));
int digiPos = line.indexOf(',', dstPos + 3);
if (digiPos > dstPos && digiPos < dstPos + 10 && digiPos < endOfDigiPos) {
f.dest = new AX25Callsign(line.substring(dstPos + 1, digiPos));
int numDigis = 1;
int nextDigiPos = digiPos + 1;
while ((nextDigiPos = line.indexOf(',', nextDigiPos + 1)) > 0 && nextDigiPos < endOfDigiPos) {
numDigis++;
}
f.digipeaters = new AX25Callsign[numDigis];
for (int i = 0; i < numDigis; i++) {
nextDigiPos = line.indexOf(',', digiPos + 1);
String digi;
if (nextDigiPos > digiPos) {
digi = line.substring(digiPos + 1, nextDigiPos);
digiPos = nextDigiPos; // move forward to next digi
} else {
digi = line.substring(digiPos + 1, endOfDigiPos).trim();
}
boolean hasBeenRepeated = false;
if (digi.endsWith("*")) {
hasBeenRepeated = true;
digi = digi.substring(0, digi.length() - 1);
}
f.digipeaters[i] = new AX25Callsign(digi);
if (hasBeenRepeated) {
for (int j = 0; j <= i; j++) {
f.digipeaters[j].h_c = true;
}
}
}
} else {
f.dest = new AX25Callsign(line.substring(dstPos + 1, endOfDigiPos).trim());
}
if (datePos > 0) { // we have a Yaesu
int endDatePos = line.indexOf(']', datePos + 1);
Date rcvTime = sdfTimestamp.parse(line.substring(datePos + 1, endDatePos));
f.rcptTime = rcvTime.getTime();
} else { // we probably have a Kantronics TNC in command mode
f.rcptTime = System.currentTimeMillis();
}
int endPidPod = line.indexOf('>', pidPos + 1);
int midPidPos = line.indexOf(' ', pidPos + 1);
String pidStr = line.substring(pidPos + 1, midPidPos > pidPos && midPidPos < endPidPod ? midPidPos : endPidPod);
int uType;
if ((uType = AX25Frame.findUTypeByName(pidStr)) >= 0) {
f.ctl = (byte) ((uType << AX25Frame.SHIFT_UTYPE) | AX25Frame.FRAMETYPE_U);
f.pid = AX25Frame.PID_NOLVL3;
} else if (pidStr.equals("I")) {
f.ctl = AX25Frame.FRAMETYPE_I;
f.pid = AX25Frame.PID_NOLVL3;
} else {
uType = AX25Frame.findSTypeByName(pidStr);
if (uType >= 0) {
f.ctl = (byte)(AX25Frame.FRAMETYPE_S | ((uType << AX25Frame.SHIFT_STYPE)));
} else {
f.ctl = AX25Frame.FRAMETYPE_S; // this is weird that it didn't have a STYPE
}
}
if (midPidPos > 0 && midPidPos < endPidPod - 1 && "R".equals(line.substring(midPidPos + 1, endPidPod).trim())) {
f.dest.h_c = true;
f.sender.h_c = false;
} else {
f.dest.h_c = false;
f.sender.h_c = true;
}
AX25Frame.initializeCmd(f);
f.sourcePort = this;
rcvdFrame = f;
// ensure we flush this packet in a reasonable amount of time, instead
// of waiting for the next packet to push it along
if (serialPort != null) { // only do this for real-time serial data, not file playback
yaesuTimerTask.resched(yaesuTimer, (int) ((256 * RF_SEND_TIME_PER_BYTE_9600 * currentCfg.baudRate) / 7680)); // pad for async stop bits
}
} catch (Exception e) {
System.out.println(new Date().toString() + ": unable to handle " + (wPtr - 1) + "-byte frame:");
StringBuilder b = new StringBuilder(wPtr * 3);
for (int i = 0; i < wPtr; i++) {
b.append(' ').append(Integer.toString(rcvBuf[i] & 0xFF, 16));
}
System.out.println(b.toString());
e.printStackTrace(System.out);
}
} else {
// finish filling in the frame body
if (line.length() > 0) {
if (rcvdFrame.body == null) {
rcvdFrame.body = line.getBytes(StandardCharsets.UTF_8);
} else {
// some dingbat sent an APRS packet with an embedded line break
// problem with this is that last packet will be delayed until
// first line of next packet arrives, so we have the timer to
// encourage flushing, since there is no reason for the radio to
// delay later lines in the same packet
byte[] extension = line.getBytes(StandardCharsets.UTF_8);
int oldBodyLen = rcvdFrame.body.length;
byte[] tmp = new byte[oldBodyLen + 1 + extension.length];
System.arraycopy(rcvdFrame.body, 0, tmp, 0, oldBodyLen);
tmp[oldBodyLen] = '\r';
System.arraycopy(extension, 0, tmp, oldBodyLen + 1, extension.length);
rcvdFrame.body = tmp;
}
} else {
// pass built frame to AX25Stack for further analysis
flushFrame();
fireReceiving(false);
}
}
wPtr = 0;
break;
default:
if (wPtr == 0) {
fireReceiving(true);
}
rcvBuf[wPtr++] = (byte) newData;
break;
}
}
} catch (Exception e) {
stats.numBadRcvFrames++;
wPtr = 0;
}
} else {
System.out.println("other SerialPortEvent=" + event.getEventType());
}
}
void flushFrame() {
// flush the previous frame, we've gotten to the end of the previous frame's body
// pass built frame to AX25Stack for further analysis
fireConsumeFrame(rcvdFrame, rcvdFrame.rcptTime);
stats.numRcvFrames++;
stats.numRcvBytes += rcvdFrame.getEstimatedBitCount() / 8;
yaesuTimerTask.cancel();
rcvdFrame = null;
}
private static boolean looksLikeFirstLine(String line) {
int dstPos = line.indexOf('>');
int datePos = line.indexOf('[', dstPos + 1);
int endDatePos = line.indexOf(']', datePos + 10);
int pidPos = line.indexOf('<', endDatePos + 1);
return (dstPos > 0 && dstPos <= 9 && datePos > dstPos && endDatePos == datePos + 18 && pidPos > datePos);
}
/**
* Returns a string representation of the object.
*
* @return a string representation of the object.
*/
@Override
public String toString() {
if (cachedToString == null) {
cachedToString = TYPE_NAME + ": " + (serialPort == null ? currentCfg.deviceName : serialPort.getPortName()) + '[' + ']';
}
return cachedToString;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

View File

@ -0,0 +1,36 @@
package org.ka2ddo.yaac.aprs;
/*
* Copyright (C) 2011-2019 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
/**
* This interface defines events that can occur when the StationTracker class is
* updated with digipeater information.
* @author Andrew Pavlin, KA2DDO
*/
public interface DigipeatListener {
/**
* Report when a new digipeater is identified.
* @param digipeater String callsign of digipeater
*/
void digipeaterAdded(String digipeater);
/**
* Report when a digipeater is used again.
* @param digipeater String callsign of digipeater
*/
void digipeaterUsedAgain(String digipeater);
}

View File

@ -0,0 +1,456 @@
package org.ka2ddo.yaac.gui;
/*
* Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import com.bbn.openmap.gui.MapPanelChild;
import com.bbn.openmap.MapBean;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.event.ProjectionListener;
import com.bbn.openmap.event.ProjectionEvent;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import org.ka2ddo.ax25.AX25Stack;
import org.ka2ddo.yaac.YAAC;
import org.ka2ddo.yaac.gps.GPSDistributor;
import org.ka2ddo.gps.GpsFix;
import org.ka2ddo.yaac.io.BeaconData;
import org.ka2ddo.util.DistanceUnit;
/**
* This panel of widgets is a replacement for the default zoom controls
* provided with OpenMap. It is specific to YAAC because it taps into the
* GPSDistributor and BeaconData.
* @see GPSDistributor
* @see BeaconData
*
* @author Andrew Pavlin, KA2DDO
*/
public class ZoomControl extends JPanel implements MapPanelChild {
private static final Font BUTTON_FONT = new Font("Sanserif", Font.BOLD, 15);
private final JComponent preferredParent;
private JPanel pBlinkenLights;
private MemoryGCPanel memoryGCPanel;
JLabel lFreezeBacklog;
Timer freezeRefreshTimer = null;
JButton bBeaconNow;
Scale scale;
JToggleButton bThaw;
JToggleButton bFreeze;
MapBean mapBean;
/**
* Create a ZoomControl widget for the MapBean's toolbar.
*
* @param mapBean MapBean to attach the widget to
* @param showMem boolean true if heap consumption widget should be displayed on the toolbar
* @param showFreezeThaw boolean true if freeze and thaw toggle buttons should be added to toolbar
* @see MemoryGCPanel
*/
public ZoomControl(MapBean mapBean, boolean showMem, boolean showFreezeThaw) {
super(new FlowLayout(FlowLayout.LEADING));
this.mapBean = mapBean;
ResourceBundle msgBundle = YAAC.getMsgBundle();
JPanel pZoomButtons = new JPanel(new GridLayout(1, 0, 5, 0));
JButton bZoomIn = new JButton("+");
bZoomIn.setToolTipText(msgBundle.getString("button.ZoomIn"));
bZoomIn.setFont(BUTTON_FONT);
pZoomButtons.add(bZoomIn);
bZoomIn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
zoomIn(2.0f);
ZoomControl.this.mapBean.requestFocusInWindow();
}
});
JButton bZoomOut = new JButton("-");
bZoomOut.setToolTipText(msgBundle.getString("button.ZoomOut"));
bZoomOut.setFont(BUTTON_FONT);
pZoomButtons.add(bZoomOut);
bZoomOut.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
zoomOut(2.0f);
ZoomControl.this.mapBean.requestFocusInWindow();
}
});
JButton bPanHome = new JButton(GuiSymbols.getStationIcon('/', '-'));
bPanHome.setToolTipText(msgBundle.getString("button.PanHome"));
pZoomButtons.add(bPanHome);
bPanHome.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
home();
ZoomControl.this.mapBean.requestFocusInWindow();
}
});
JButton bPan00 = new JButton(GuiSymbols.getStationIcon('/', '0'));
bPan00.setToolTipText(msgBundle.getString("button.Pan00"));
pZoomButtons.add(bPan00);
bPan00.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
ZoomControl.this.mapBean.setCenter(0.0F, 0.0F);
ZoomControl.this.mapBean.requestFocusInWindow();
}
});
bBeaconNow = new JButton(new BeaconNowAction());
bBeaconNow.setToolTipText(msgBundle.getString("button.BeaconNow"));
pZoomButtons.add(bBeaconNow);
Timer beaconButtonColorTimer = new Timer(5000, new BeaconNowTimerListener(bBeaconNow));
beaconButtonColorTimer.setInitialDelay(1000);
beaconButtonColorTimer.setRepeats(true);
beaconButtonColorTimer.start();
System.out.println(new Date().toString() + ": adding map scale widget...");
add(pZoomButtons);
add(scale = new Scale(mapBean));
// add blinkenlights panel to indicate various status changes in YAAC
System.out.println(new Date().toString() + ": adding blinkenlight panel...");
add(pBlinkenLights = new JPanel(new FlowLayout(FlowLayout.LEADING, 3, 5)));
add(memoryGCPanel = new MemoryGCPanel());
setMemoryGCPanelVisible(showMem);
if (showFreezeThaw) {
ButtonGroup bgFreeze = new ButtonGroup();
bThaw = new JToggleButton(new ImageIcon(IconLoader.loadImage("images/go.png")));
bThaw.setSelectedIcon(new ImageIcon(IconLoader.loadImage("images/go_selected.png")));
String s = msgBundle.getString("button.thaw");
bThaw.setToolTipText(s);
bThaw.getAccessibleContext().setAccessibleDescription(s);
bThaw.setSelected(true);
bThaw.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (bThaw.isSelected()) {
AX25Stack.getInstance().setFrozen(false);
}
ZoomControl.this.mapBean.requestFocusInWindow();
}
});
add(bThaw);
bgFreeze.add(bThaw);
bFreeze = new JToggleButton(new ImageIcon(IconLoader.loadImage("images/stop.png")));
bFreeze.setSelectedIcon(new ImageIcon(IconLoader.loadImage("images/stop_selected.png")));
s = msgBundle.getString("button.freeze");
bFreeze.setToolTipText(s);
bFreeze.getAccessibleContext().setAccessibleDescription(s);
bFreeze.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (bFreeze.isSelected()) {
AX25Stack.getInstance().setFrozen(true);
// start refresh timer for stack queue backlog
freezeRefreshTimer = new Timer(3000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
AX25Stack ax25Stack = AX25Stack.getInstance();
if (!ax25Stack.isFrozen()) {
lFreezeBacklog.setText(" ");
freezeRefreshTimer.stop();
freezeRefreshTimer = null;
} else {
int currentBacklog = ax25Stack.getCurrentBacklog();
int maxQueueCapacity = ax25Stack.getMaxQueueCapacity();
lFreezeBacklog.setText(MessageFormat.format("{0} of {1}", currentBacklog, maxQueueCapacity));
lFreezeBacklog.setForeground(currentBacklog > (maxQueueCapacity * 3) / 4 ? Color.RED : Color.BLACK);
}
}
});
freezeRefreshTimer.setRepeats(true);
freezeRefreshTimer.start();
}
ZoomControl.this.mapBean.requestFocusInWindow();
}
});
add(bFreeze);
bgFreeze.add(bFreeze);
lFreezeBacklog = new JLabel(" ");
add(lFreezeBacklog);
}
preferredParent = (JComponent)mapBean.getParent();
}
/**
* Pan the map so the local station is at the center.
*/
public void home() {
BeaconData beaconData = YAAC.getBeaconData();
GpsFix fix = GPSDistributor.getInstance().getCurrentFix();
if (!beaconData.useGpsForPosition ||
fix.timestamp < System.currentTimeMillis() - (60 * 60 * 1000L)) {
// too old to look at
fix = null;
}
if (null != fix) {
mapBean.setCenter((float) fix.latitude, (float) fix.longitude);
} else {
if (beaconData.latitude != 0 && beaconData.longitude != 0) {
mapBean.setCenter((float) beaconData.latitude, (float) beaconData.longitude);
}
}
}
/**
* Reduce the map scale by the specified zoom factor.
* @param zoomFactor ratio to divide the current map scale by
*/
public void zoomIn(float zoomFactor) {
mapBean.setScale(mapBean.getScale() / zoomFactor);
}
/**
* Enlarge the map scale by the specified zoom factor.
* @param zoomFactor ratio to multiply the current map scale by
*/
public void zoomOut(float zoomFactor) {
mapBean.setScale(mapBean.getScale() * zoomFactor);
}
/**
* Get where on the map bean this control widget wants to be added.
* @return always on the North side
*/
public String getPreferredLocation() {
return BorderLayout.NORTH;
}
/**
* No-op, because this widget is hard-wired to want to be at the top edge of the map.
* @param string ignored name of relative position on MapBean to place this widget
*/
public void setPreferredLocation(String string) {
}
public String getParentName() {
return preferredParent.getName();
}
/**
* Get the current value of the map scale display on this widget.
* @return float length of map scale
*/
public float getScale() {
return scale.getScale();
}
private static class Scale extends JComponent implements ProjectionListener, ComponentListener {
private final MapBean mapBean;
private transient Projection projection = null;
private final Font LABEL_FONT = new Font("Sanserif", Font.PLAIN, 10);
private final DecimalFormat NUMBER_FORMAT = new DecimalFormat("0");
private final DecimalFormat SMALL_NUMBER_FORMAT = new DecimalFormat("0.0");
private final DecimalFormat VERY_SMALL_NUMBER_FORMAT = new DecimalFormat("0.00");
private float dist = 0.1f;
Scale(MapBean mapBean) {
this.mapBean = mapBean;
mapBean.addProjectionListener(this);
setFont(LABEL_FONT);
mapBean.addComponentListener(this);
setMinimumSize(new Dimension(50, 18));
}
public void projectionChanged(ProjectionEvent e) {
this.projection = e.getProjection();
recalculateScale();
}
public void componentHidden(ComponentEvent e) {
// do nothing
}
public void componentMoved(ComponentEvent e) {
// do nothing
}
public void componentResized(ComponentEvent e) {
if (projection != null) {
recalculateScale();
}
}
public void componentShown(ComponentEvent e) {
// do nothing
}
private void recalculateScale() {
Point2D llp = mapBean.getCenter();
Dimension mapSize = mapBean.getSize();
int y = mapSize.height / 2;
int maxDelta = Math.min(mapSize.width, mapSize.height) / 3;
// figure out reasonably visible map scale in statute miles or kilometers as specified by preferences
LatLonPoint llp2 = projection.inverse(mapSize.width / 2, y - maxDelta);
dist = (llp2.getLatitude() - (float)llp.getY()) * YAAC.getDistanceUnit().from(DistanceUnit.DEG_LAT);
/*
* figure out how much padding will be needed for the numbers on the scale:
* width specified for scale plus half the string width of the scale number,
* height specified as font height plus same for scale bar.
*/
FontMetrics fm = getFontMetrics(LABEL_FONT);
Dimension d = new Dimension(maxDelta + 2, fm.getHeight() * 2);
setPreferredSize(d);
setMaximumSize(d);
revalidate();
repaint();
}
@Override
protected void paintComponent(Graphics g) {
if (projection != null) {
g.setColor(getBackground());
Dimension d = getSize();
g.fillRect(0, 0, d.width, d.height);
Point2D llp = mapBean.getCenter();
Dimension mapSize = mapBean.getSize();
int y = mapSize.height / 2;
int maxDelta = Math.min(mapSize.width, mapSize.height) / 3 - 2;
LatLonPoint llp2 = projection.inverse(mapSize.width / 2, y - maxDelta);
dist = (llp2.getLatitude() - (float)llp.getY()) * YAAC.getDistanceUnit().from(DistanceUnit.DEG_LAT);
double exp = Math.log10(dist);
int powerOf10 = (int)Math.floor(exp);
double idealDist = Math.pow(10.0, powerOf10);
if (idealDist < dist / 2) {
// too short, try a longer scale of 2 or 5 times the ideal
if (5 * idealDist > dist) {
idealDist *= 2;
} else {
idealDist *= 5;
}
}
int delta = (int)Math.round(maxDelta * (idealDist / dist));
g.setColor(getForeground());
FontMetrics fm = getFontMetrics(LABEL_FONT);
g.drawRect(1, fm.getHeight() / 2, delta, fm.getHeight() / 2);
g.fillRect(1, fm.getHeight() / 2, delta / 2, fm.getHeight() / 2);
g.drawString("0", 0, fm.getHeight() * 2);
String label;
if (powerOf10 >= 1) {
label = NUMBER_FORMAT.format(idealDist) + ' ' + YAAC.getDistanceUnit().name().toLowerCase(Locale.getDefault());
} else if (powerOf10 == 0) {
label = SMALL_NUMBER_FORMAT.format(idealDist) + ' ' + YAAC.getDistanceUnit().name().toLowerCase(Locale.getDefault());
} else {
label = VERY_SMALL_NUMBER_FORMAT.format(idealDist) + ' ' + YAAC.getDistanceUnit().name().toLowerCase(Locale.getDefault());
}
g.drawString(label, 1 + delta - fm.stringWidth(label), fm.getHeight() * 2);
}
}
public float getScale() {
return dist;
}
}
/**
* Get the small panel in the north toolbar for displaying blinkenlights (small status lights).
* @return blinkenlights JPanel
*/
public JPanel getBlinkenLightsPanel() {
return pBlinkenLights;
}
/**
* Change the visibility of the memory usage status monitor.
* @param visible boolean true if memory status monitor should be visible
*/
public void setMemoryGCPanelVisible(boolean visible) {
memoryGCPanel.setVisible(visible);
}
/**
* Report the current visibility of the MemoryGCPanel.
* @return boolean true if panel is visible
*/
public boolean isMemoryGCPanelVisible() {
return memoryGCPanel.isVisible();
}
/**
* Timer listener to update the appearance of the BCN ("Beacon Now") button depending on how long
* since a beacon has been transmitted, just as a hint to the user to not overdo it.
* @author Andrew Pavlin, KA2DDO
*/
private static class BeaconNowTimerListener implements ActionListener {
private final JButton bBeaconNow;
private final Map<String, BeaconData> beaconMap = YAAC.getBeaconDataMap();
private final Color quarterWayColor;
private final Color halfWayColor;
private final Color quarter3WayColor;
BeaconNowTimerListener(JButton bBeaconNow) {
this.bBeaconNow = bBeaconNow;
UIDefaults uiDefaults = UIManager.getLookAndFeel().getDefaults();
Color controlBkgrndColor = uiDefaults.getColor("control");
if (controlBkgrndColor == null) {
controlBkgrndColor = SystemColor.control;
}
Color controlTextColor = uiDefaults.getColor("controlText");
if (controlTextColor == null) {
controlTextColor = SystemColor.controlText;
}
quarterWayColor = new Color((7 * controlBkgrndColor.getRed() + controlTextColor.getRed()) / 8,
(7 * controlBkgrndColor.getGreen() + controlTextColor.getGreen()) / 8,
(7 * controlBkgrndColor.getBlue() + controlTextColor.getBlue()) / 8,
(7 * controlBkgrndColor.getAlpha() + controlTextColor.getAlpha()) / 8);
halfWayColor = new Color((3 * controlBkgrndColor.getRed() + controlTextColor.getRed()) / 4,
(3 * controlBkgrndColor.getGreen() + controlTextColor.getGreen()) / 4,
(3 * controlBkgrndColor.getBlue() + controlTextColor.getBlue()) / 4,
(3 * controlBkgrndColor.getAlpha() + controlTextColor.getAlpha()) / 4);
quarter3WayColor = new Color((controlBkgrndColor.getRed() + controlTextColor.getRed()) / 2,
(controlBkgrndColor.getGreen() + controlTextColor.getGreen()) / 2,
(controlBkgrndColor.getBlue() + controlTextColor.getBlue()) / 2,
(controlBkgrndColor.getAlpha() + controlTextColor.getAlpha()) / 2);
}
@Override
public void actionPerformed(ActionEvent e) {
long mostRecentBeaconTime = 0L;
boolean foundEnabledBeacon = false;
for (BeaconData bd : beaconMap.values()) {
if (bd.isEnabled()) {
foundEnabledBeacon = true;
long lastBeaconTime = bd.getLastSendTime();
if (lastBeaconTime > mostRecentBeaconTime) {
mostRecentBeaconTime = lastBeaconTime;
}
}
}
if (foundEnabledBeacon) {
if (!bBeaconNow.isEnabled()) {
bBeaconNow.setEnabled(true);
}
long delta = System.currentTimeMillis() - mostRecentBeaconTime;
bBeaconNow.setForeground(delta < 5000L ? SystemColor.control :
(delta < 15000L ? quarterWayColor :
(delta < 30000L ? halfWayColor :
(delta < 60000L ? quarter3WayColor : SystemColor.controlText))));
} else {
if (bBeaconNow.isEnabled()) {
bBeaconNow.setEnabled(false);
}
}
}
}
}

View File

@ -0,0 +1,59 @@
package org.ka2ddo.yaac.webserver;
/*
* Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.util.UTF8StreamWriter;
import java.io.*;
import java.util.Iterator;
import java.util.LinkedHashMap;
/**
* This page produces a robots.txt file contents to try to prevent web spiders
* from navigating the entire website in YAAC.
* @author Andrew Pavlin, KA2DDO
*/
public class RobotsTxtPage extends PathHandler implements CacheablePage {
/**
* Given an inbound HTTP request, generate a page using its parameters.
*
* @param hct HttpConnectionThread issuing the call
* @param requestHeaders the HTTP request headers the browser sent
* @param in DataInput for reading the body of the HTTP request
* @param out OutputStream to write the generated page to
* @param path the remainder of the URL following the prefix identifying this particular page, in case the user is providing parameters to the page
* @return the Content-Type header value to return to tell the remote browser how to interpret the page
* @throws java.io.IOException if the page could not be generated for some reason
*/
@Override
public String processPage(HttpConnectionThread hct, LinkedHashMap<String, String> requestHeaders, DataInput in, OutputStream out, String path) throws IOException {
PrintWriter pOut = new PrintWriter(new UTF8StreamWriter(out));
pOut.print("User-agent: *"); pOut.print(HttpConnectionThread.CRLF);
HttpServer server = hct.getServerInstance();
for (Iterator<String> it = server.getPathIterator(); it.hasNext(); ) {
String page = it.next();
if (!"/".equals(page) && !page.startsWith("/index.") && !"/robots.txt".equals(page)) {
pOut.print("Disallow: "); pOut.print(page); pOut.print(HttpConnectionThread.CRLF);
}
}
pOut.flush();
return "text/plain; charset=UTF-8";
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

View File

@ -0,0 +1,27 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>View GPS Status</title>
</head>
<body lang="en-US" dir="LTR">
<h1>View GPS Status</h1>
<p>YAAC allows you to monitor the current status of your GPS
receiver. Select the menu item View -&gt; Show GPS Status. It will
display a popup window like this, reporting the current status of
your received data. If the GPS receiver is not working, all of the
fields will be blank.</p>
<p><img src="./gpsstatus1.png" alt="GPS Position report"/>
</p>
<p>If your GPS receiver also provides $GPGSA and/or $GPGSV records,
you can also look at a current sky view of the visible satellite
constellation by selecting the Visibility tab of the popup window.</p>
<p><img src="./gpsstatus2.png" alt="GPS Sky View report"/>
</p>
<p>If the $GPGSA record is provided by your GPS receiver, YAAC will
indicate the satellites are reported as currently being used by the
GPS receiver for location processing by highlighting them in green.</p>
<p>Close the window with the normal title bar controls to get rid of
it.</p>
</body>
</html>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>commons-math3</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
<name>Commons Math3</name>
<description>
Commons Math3 is a library of lightweight, self-contained mathematics and statistics components addressing
the most common problems not available in the Java programming language or Commons Lang.
</description>
<url>http://commons.apache.org/math/</url>
<issueManagement>
<system>jira</system>
<url>http://issues.apache.org/jira/browse/math</url>
</issueManagement>
<scm>
<connection>scm:svn:http://svn.apache.org/repos/asf/commons/proper/math/branches/math-3.x/</connection>
<developerConnection>scm:svn:https://svn.apache.org/repos/asf/commons/proper/math/branches/math-3.x/</developerConnection>
<url>http://svn.apache.org/viewvc/commons/proper/math/branches/math-3.x/</url>
</scm>
<dependencies>
<!-- used for unit tests -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compile.source>1.4</maven.compile.source>
<maven.compile.target>1.4</maven.compile.target>
<commons.componentid>math3</commons.componentid>
<commons.release.version>3.6.1</commons.release.version>
<commons.release.name>commons-math3-3.6.1</commons.release.name>
<commons.osgi.symbolicName>org.apache.commons.math3</commons.osgi.symbolicName>
<commons.deployment.protocol>svn</commons.deployment.protocol>
<commons.binary.suffix />
<commons.jira.id>math</commons.jira.id>
</properties>
</project>

View File

@ -0,0 +1,48 @@
package org.ka2ddo.yaac.gui.config;
/*
* Copyright (C) 2011-2017 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.yaac.gui.FontCache;
import org.ka2ddo.yaac.io.PortConnector;
import javax.swing.*;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.util.List;
/**
* This class renders a port name in a JTable.
*/
class PortCellRenderer extends JLabel implements TableCellRenderer {
private final List<PortConnector> portList;
PortCellRenderer(List<PortConnector> portList) {
this.portList = portList;
setOpaque(true);
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
setFont(FontCache.getInstance().getFont(Font.DIALOG));
setText((String) value);
setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
PortConnector connector = portList.get(row);
setForeground((connector == null || !connector.isOpen()) ? Color.RED : (isSelected ? table.getSelectionForeground() : table.getForeground()));
return this;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,45 @@
package org.ka2ddo.yaac.io;
/*
* Copyright (C) 2011-2012 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
/**
* This interface defines a listener that wants to know the details of when a
* port is receiving or sending frames of data.
*/
public interface PortEventListener {
/**
* Reports when the specified port starts or stops transmitting a message.
* @param port PortConnector of relevant port
* @param isTransmitting boolean true if transmitting is starting, false if transmitting is ending
*/
public void portTransmitting(PortConnector port, boolean isTransmitting);
/**
* Reports when the specified port starts or stops receiving a message.
* @param port PortConnector of relevant port
* @param isReceiving boolean true if receiving is starting, false if receiving is ending
*/
public void portReceiving(PortConnector port, boolean isReceiving);
/**
* Reports when the port fails for some reason.
* @param port PortConnector of relevant port
*/
public void portFailed(PortConnector port);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

View File

@ -0,0 +1,119 @@
package org.ka2ddo.yaac.gui.table;
/*
* Copyright (C) 2011-2018 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.aprs.Symbols;
import org.ka2ddo.yaac.gui.GuiSymbols;
import org.ka2ddo.yaac.gui.SelectableSymbol;
import javax.swing.*;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
/**
* This class implements a renderer for SelectableSymbols that shows both the icon and
* the characters representing the icon in the network packets.
*/
public class SelectableSymbolRenderer extends JLabel implements TableCellRenderer {
private final boolean alwaysShowSymbolChars;
/**
* Create a SelectableSymbolRenderer that only shows the symbol characters when it is an overlay.
*/
public SelectableSymbolRenderer() {
setOpaque(true);
alwaysShowSymbolChars = false;
}
/**
* Create a SelectableSymbolRenderer that selectively decides whether to show the symbol characters.
* @param alwaysShowSymbolChars boolean true to always show symbol characters, else only if overlay
*/
public SelectableSymbolRenderer(boolean alwaysShowSymbolChars) {
setOpaque(true);
this.alwaysShowSymbolChars = alwaysShowSymbolChars;
}
/**
* Returns the component used for drawing the cell. This method is
* used to configure the renderer appropriately before drawing.
*
* @param table the <code>JTable</code> that is asking the
* renderer to draw; can be <code>null</code>
* @param value the value of the cell to be rendered. It is
* up to the specific renderer to interpret
* and draw the value. For example, if
* <code>value</code>
* is the string "true", it could be rendered as a
* string or it could be rendered as a check
* box that is checked. <code>null</code> is a
* valid value
* @param isSelected true if the cell is to be rendered with the
* selection highlighted; otherwise false
* @param hasFocus if true, render cell appropriately. For
* example, put a special border on the cell, if
* the cell can be edited, render in the color used
* to indicate editing
* @param row the row index of the cell being drawn. When
* drawing the header, the value of
* <code>row</code> is -1
* @param column the column index of the cell being drawn
*/
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
SelectableSymbol ss = (SelectableSymbol)value;
Symbols.SymbolAttr symbolAttr = null;
if (ss != null) {
symbolAttr = Symbols.getSymbolAttr(ss.symTableId, ss.symbolCode);
}
if (ss != null && alwaysShowSymbolChars) {
setText(new String(new char[]{ss.symTableId, ss.symbolCode}));
if (symbolAttr != null) {
if (symbolAttr.img != null) {
setIcon(((GuiSymbols.GuiSymbolImage)symbolAttr.img).getIcon());
} else {
setIcon(GuiSymbols.getStationIcon('\\', ss.symbolCode));
}
setToolTipText(symbolAttr.getTypeName());
} else {
setIcon(null);
setToolTipText("");
}
} else if (symbolAttr != null) {
if (ss.symTableId != symbolAttr.getAprsSymTableId()) {
setText(new String(new char[]{ss.symTableId}));
} else {
setText("");
}
setIcon(GuiSymbols.getStationIcon(ss.symTableId, ss.symbolCode));
setToolTipText(symbolAttr.getTypeName());
} else {
setText("");
setIcon(null);
setToolTipText("");
}
if (isSelected) {
setBackground(table.getSelectionBackground());
setForeground(table.getSelectionForeground());
} else {
setBackground(table.getBackground());
setForeground(table.getForeground());
}
return this;
}
}

View File

@ -0,0 +1,19 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>View Tx I-Gated Stations</title>
</head>
<body lang="en-US" dir="LTR">
<h1>View Tx I-Gated Stations</h1>
<p>This view reports the stations that YAAC is I-gating to the local
RF network from the Internet.</p>
<img src="./viewtxigate.png"/>
<p>For each station, the view reports the Internet sender's callsign,
the last time a message was forwarded for the sender, the addressee
of the last such message (which is for some local RF station),
the last time a position message of some sort was forwarded from the
sender, and the last time any other traffic was forwarded on behalf
of the Internet sender due to <a href="./config_transmit.html#supplementalTxIgate">supplemental Tx I-gate filter settings</a>.</p>
</body>
</html>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE index
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Index Version 2.0//EN"
"http://java.sun.com/products/javahelp/index_2_0.dtd">
<!--
Copyright (C) 2011-2021 Andrew Pavlin, KA2DDO
This file is part of YAAC (Yet Another APRS Client).
YAAC is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
YAAC 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
and GNU Lesser General Public License along with YAAC. If not,
see <http://www.gnu.org/licenses/>.
-->
<index version="2.0">
<indexitem text="AREDN, configuring" target="configure.aredn"/>
<indexitem text="configuring YAAC">
<indexitem text="AREDN" target="configure.aredn"/>
</indexitem>
</index>

View File

@ -0,0 +1,147 @@
package org.ka2ddo.yaac.gui;
/*
* Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
* This file is part of YAAC.
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.yaac.ax25.Age;
import javax.swing.*;
/**
* This model allows spinning for a specific Age value.
* @author Andrew Pavlin, KA2DDO
*/
public class AgeSpinnerModel extends AbstractSpinnerModel {
private transient long value;
private long stepSize = 5000L;
/**
* Create an AgeSpinnerModel with the specified initial value and step size.
* @param value long value in milliseconds
* @param stepSize long step size in milliseconds
* @throws IllegalArgumentException if value or step size are not valid
*/
public AgeSpinnerModel(long value, long stepSize) throws IllegalArgumentException {
setStepSize(stepSize);
setValue(value);
}
/**
* Create an AgeSpinnerModel with the specified initial value and default step size of 5 seconds.
* @param value Age value in milliseconds
* @throws IllegalArgumentException if value is not valid
*/
public AgeSpinnerModel(Age value) throws IllegalArgumentException {
setValue(value);
}
/**
* Change the step size of the model.
* @param stepSize long step size in milliseconds
* @throws IllegalArgumentException if step size is not valid or existing value not consistent with new step size
*/
public void setStepSize(long stepSize) throws IllegalArgumentException {
if (stepSize < 1000L || (stepSize % 1000L) != 0) {
throw new IllegalArgumentException("step size=" + stepSize + " not a positive multiple of 1000 milliseconds");
}
if ((value % stepSize) != 0) {
throw new IllegalArgumentException("old value=" + value + " not a multiple of new stepSize=" + stepSize);
}
this.stepSize = stepSize;
}
/**
* The <i>current element</i> of the sequence. This element is usually
* displayed by the <code>editor</code> part of a <code>JSpinner</code>.
*
* @return the current spinner value.
* @see #setValue
*/
@Override
public Object getValue() {
return new Age(value);
}
/**
* Changes current value of the model, typically this value is displayed
* by the <code>editor</code> part of a <code>JSpinner</code>.
* If the <code>SpinnerModel</code> implementation doesn't support
* the specified value then an <code>IllegalArgumentException</code>
* is thrown. For example a <code>SpinnerModel</code> for numbers might
* only support values that are integer multiples of ten. In
* that case, <code>model.setValue(new Number(11))</code>
* would throw an exception.
*
* @param value new value for the model
* @throws IllegalArgumentException if <code>value</code> isn't allowed
* @see #getValue
*/
@Override
public void setValue(Object value) {
long tmp;
if (value instanceof Long) {
tmp = ((Long)value).longValue();
} else if (value instanceof String) {
tmp = Age.parseAge((String) value);
} else if (value instanceof Age) {
tmp = ((Age)value).getAge();
} else {
throw new IllegalArgumentException("unrecognized value class " + value.getClass().getName());
}
if (tmp < 0L) {
throw new IllegalArgumentException("new value=" + tmp + " is negative");
}
if ((tmp % stepSize) != 0) {
throw new IllegalArgumentException("new value=" + tmp + " not a multiple of stepSize=" + stepSize);
}
if (this.value != tmp) {
this.value = tmp;
fireStateChanged();
}
}
/**
* Return the object in the sequence that comes after the object returned
* by <code>getValue()</code>. If the end of the sequence has been reached
* then return null. Calling this method does not effect <code>value</code>.
*
* @return the next legal value or null if one doesn't exist
* @see #getValue
* @see #getPreviousValue
*/
@Override
public Object getNextValue() {
return new Age(this.value + stepSize);
}
/**
* Return the object in the sequence that comes before the object returned
* by <code>getValue()</code>. If the end of the sequence has been reached then
* return null. Calling this method does not effect <code>value</code>.
*
* @return the previous legal value or null if one doesn't exist
* @see #getValue
* @see #getNextValue
*/
@Override
public Object getPreviousValue() {
if (value > 0L) {
return new Age(Math.max(this.value - stepSize, 0L));
}
return null;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,119 @@
package org.ka2ddo.yaac.gui.filter;
/*
* Copyright (C) 2011-2019 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.yaac.YAAC;
import org.ka2ddo.yaac.filter.Filter;
import org.ka2ddo.yaac.filter.FilterChangeListener;
import javax.swing.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.Closeable;
import java.io.IOException;
import java.util.ResourceBundle;
/**
* This Filter selects viewing messages that are only valid or only invalid.
* @author Andrew Pavlin, KA2DDO
*/
public class ValidInvalidFilter extends JPanel implements FilterChangeListener, Closeable {
transient final org.ka2ddo.yaac.filter.ValidInvalidFilter filter;
final JRadioButton rbAll;
final JRadioButton rbValid;
final JRadioButton rbInvalid;
/**
* Create a UI for controlling a ValidInvalidFilter.
* @param myFilter the ValidInvalidFilter to control
* @see org.ka2ddo.yaac.filter.ValidInvalidFilter
*/
public ValidInvalidFilter(org.ka2ddo.yaac.filter.ValidInvalidFilter myFilter) {
this.filter = myFilter;
ResourceBundle msgBundle = YAAC.getMsgBundle();
ButtonGroup bg = new ButtonGroup();
rbAll = new JRadioButton(msgBundle.getString("filter.ValidInvalid.all"));
add(rbAll);
bg.add(rbAll);
if (org.ka2ddo.yaac.filter.ValidInvalidFilter.STATE_ALL == filter.getState()) {
rbAll.setSelected(true);
}
rbAll.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (rbAll.isSelected()) {
filter.setState(org.ka2ddo.yaac.filter.ValidInvalidFilter.STATE_ALL);
}
}
});
rbValid = new JRadioButton(msgBundle.getString("filter.ValidInvalid.valid"));
add(rbValid);
bg.add(rbValid);
if (org.ka2ddo.yaac.filter.ValidInvalidFilter.STATE_VALID == filter.getState()) {
rbValid.setSelected(true);
}
rbValid.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (rbValid.isSelected()) {
filter.setState(org.ka2ddo.yaac.filter.ValidInvalidFilter.STATE_VALID);
}
}
});
rbInvalid = new JRadioButton(msgBundle.getString("filter.ValidInvalid.invalid"));
add(rbInvalid);
bg.add(rbInvalid);
if (org.ka2ddo.yaac.filter.ValidInvalidFilter.STATE_INVALID == filter.getState()) {
rbInvalid.setSelected(true);
}
rbInvalid.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (rbInvalid.isSelected()) {
filter.setState(org.ka2ddo.yaac.filter.ValidInvalidFilter.STATE_INVALID);
}
}
});
filter.addFilterChangeListener(this);
}
/**
* Closes this stream and releases any system resources associated
* with it. If the stream is already closed then invoking this
* method has no effect.
*
* @throws java.io.IOException if an I/O error occurs
*/
public void close() throws IOException {
filter.removeFilterChangeListener(this);
}
/**
* Called when the specified Filter's matching criteria have been changed.
*
* @param changedFilter Filter that has changed
* @param changedByUser boolean true if change was manually made by user, false if
* change was made automatically by dynamic filter logic
*/
public void filterSettingsChanged(Filter changedFilter, boolean changedByUser) {
if (changedFilter == filter) {
int state = filter.getState();
rbAll.setSelected(org.ka2ddo.yaac.filter.ValidInvalidFilter.STATE_ALL == state);
rbValid.setSelected(org.ka2ddo.yaac.filter.ValidInvalidFilter.STATE_VALID == state);
rbInvalid.setSelected(org.ka2ddo.yaac.filter.ValidInvalidFilter.STATE_INVALID == state);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,112 @@
package org.ka2ddo.yaac.healthmonitor;
/*
* Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.ax25.AX25Callsign;
import org.ka2ddo.yaac.YAAC;
import org.ka2ddo.yaac.ax25.StationState;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Set;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
/**
* This class contains the control information for specifying one remote
* station to monitor.
* @author Andrew Pavlin, KA2DDO
*/
public class MonitoredStation {
/**
* Set of all the types of monitoring the health monitor should perform.
*/
public final Set<MonitorType> types = EnumSet.noneOf(MonitorType.class);
AX25Callsign stationCallsign;
/**
* The StationState object for the monitored station.
*/
public transient StationState state;
/**
* The list of stations digipeated by this station. May be null if this station is not a digipeater or I-gate.
*/
public transient ArrayList<String> digipeatedStations = null;
/**
* Create an empty MonitoredStation record.
*/
public MonitoredStation() {}
/**
* Create a MonitoredStation record for the specified callsign-SSID.
* @param callsign String callsign-SSID of the station to monitor
*/
public MonitoredStation(String callsign) {
stationCallsign = new AX25Callsign(callsign);
}
/**
* Get the callsign-SSID of this MonitoredStation.
* @return AX25Callsign of the station callsign-SSID
*/
public AX25Callsign getStationCallsign() {
return stationCallsign;
}
/**
* Initialize the callsign for an empty MonitoredStation record.
* @param stationCallsign AX25Callsign of the station callsign-SSID
* @throws IllegalStateException if callsign was already set for this record
*/
public void setStationCallsign(AX25Callsign stationCallsign) throws IllegalStateException {
if (this.stationCallsign != null) {
throw new IllegalStateException("callsign already set for this MonitoredStation");
}
MonitoredStationList monitoredStationList = MonitoredStationList.getInstance();
if (monitoredStationList.findStation(stationCallsign.toString()) != null) {
throw new IllegalArgumentException("duplicate callsign already in list");
}
this.stationCallsign = stationCallsign;
int index = monitoredStationList.indexOf(this);
if (index >= 0) {
monitoredStationList.addToMap(this);
monitoredStationList.fireMonitoredStationUpdated(index);
}
}
/**
* Persist this MonitoredStation to Java Preferences.
*/
public void writeToPreferences() {
Preferences prefs = YAAC.getPreferences().node(MonitoredStationList.MONITORED_STATIONS);
StringBuilder b = new StringBuilder();
for (MonitorType t : types) {
if (b.length() > 0) {
b.append(',');
}
b.append(t.name());
}
prefs.put(stationCallsign.toString(), b.toString());
try {
prefs.flush();
} catch (BackingStoreException e) {
e.printStackTrace(System.out);
}
}
}

View File

@ -0,0 +1,29 @@
package org.ka2ddo.yaac.adsbdecoder;
/*
* Copyright (C) 2011-2015 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
/**
* This interface defines callbacks so a listener can be informed of changes to the ADS-B aircraft cache.
*/
public interface AdsbCacheListener {
public void aircraftAdded(int index, AircraftState aircraftState);
public void aircraftUpdated(int index, AircraftState aircraftState);
public void aircraftRemoved(int index, AircraftState aircraftState);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

View File

@ -0,0 +1,41 @@
package org.ka2ddo.ax25;
/*
* Copyright (C) 2011-2018 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
/**
* This interface defines a means by which an arbitrary handler can choose to accept
* an inbound AX.25 connected-mode session request.
* @author Andrew Pavlin, KA2DDO
*/
public interface ConnectionRequestListener {
/**
* Decide whether to accept the specified inbound AX.25 connected-mode session request. Note
* that the state is not fully connected at the point of this call (so the called code can choose
* to reject it), so the called code should register a {@link ConnectionEstablishmentListener ConnectionEstablishmentListener} on the
* ConnState to be informed when the connection is fully established if the called code chooses to
* accept the connection request.
* @param state ConnState object describing the session being built
* @param originator AX25Callsign of the originating station
* @param port Connector through which the request was received
* @return boolean true if request should be accepted, false if not
* @see ConnState#listener
*/
public boolean acceptInbound(ConnState state, AX25Callsign originator, Connector port);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -0,0 +1,7 @@
/**
* This package provides various useful utility classes that otherwise mostly have no relationship
* with each other. These classes should not depend on any libraries other than those
* provided in the core Java Runtime Environment; any change creating such dependencies
* is a scope violation for this package.
*/
package org.ka2ddo.util;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,83 @@
package org.ka2ddo.yaac.core.queries;
/*
* Copyright (C) 2011-2021 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.aprs.Message;
import org.ka2ddo.ax25.Connector;
import org.ka2ddo.yaac.ax25.StationTracker;
import org.ka2ddo.yaac.io.PortManager;
import org.ka2ddo.yaac.pluginapi.AbstractQueryHandler;
import java.util.Arrays;
/**
* This class handles queries for the list of stations directly reachable by this station over RF.
* @author Andrew Pavlin, KA2DDO
*/
public class DirectQueryHandler extends AbstractQueryHandler {
/**
* Handle the passed in message and generate whatever appropriate response should be made.
*
* @param mm MessageMessage addressed explicitly to local station whose content begins with
* one of the prefixes specified by the subclass.
*/
public void handleQuery(Message mm) {
long now = System.currentTimeMillis();
String[] directStations = StationTracker.getInstance().getDirectStationList(now);
if (directStations != null && directStations.length > 0) {
Arrays.sort(directStations);
int totalCharLen = 0;
for (String direct : directStations) {
totalCharLen += 1 + direct.length();
}
if (totalCharLen < 59) {
// can send answer in one piece without exceeding maximum message length
StringBuilder b = new StringBuilder(totalCharLen+8).append("Directs=");
for (String s : directStations) {
b.append(' ').append(s);
}
transmitReply(mm, b.toString());
} else {
int numDirectResponses = 0;
int index = 0;
StringBuilder b = new StringBuilder("Directs(");
while (index < directStations.length) {
b.append(++numDirectResponses).append(")=");
while (index < directStations.length && b.length() < 66) {
String station = directStations[index];
if (b.length() + station.length() + 1 < 67) {
b.append(' ').append(station);
index++;
} else {
break; // can't fit any more in this line
}
}
transmitReply(mm, b.toString());
b.setLength(8); // reset to use the prefix again
}
}
} else if (PortManager.getPreferredConnector(Connector.CAP_RF) == null) {
// must be only an I-Gate connection with no "locals" because no RF
transmitReply(mm, "Directs=none (APRS-IS only)");
} else {
// we have RF with no "locals"
transmitReply(mm, "Directs=none (with RF)");
}
}
}

View File

@ -0,0 +1,160 @@
package org.ka2ddo.yaac.gui.pluginadapter;
/*
* Copyright (C) 2011-2019 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.yaac.pluginapi.AbstractMenuAction;
import org.ka2ddo.yaac.pluginapi.AbstractMenuActionPropertyListener;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
/**
* This class defines a Java Swing specific wrapper around the now GUI-independent
* AbstractMenuAction class in the plugin API package.
* @author Andrew Pavlin, KA2DDO
*/
public class MenuAction implements Action, AbstractMenuActionPropertyListener, ItemListener {
private final AbstractMenuAction ama;
private ArrayList<PropertyChangeListener> listeners;
/**
* Create the AWT/Swing-specific wrapper around an AbstractMenuAction.
* @param abstractMenuAction AbstractMenuAction to instantiate in Java Swing
*/
public MenuAction(AbstractMenuAction abstractMenuAction) {
this.ama = abstractMenuAction;
abstractMenuAction.setPropertyListener(this);
}
/**
* Adds a <code>PropertyChange</code> listener. Containers and attached
* components use these methods to register interest in this
* <code>Action</code> object. When its enabled state or other property
* changes, the registered listeners are informed of the change.
*
* @param listener a <code>PropertyChangeListener</code> object
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
if (listeners == null) {
listeners = new ArrayList<PropertyChangeListener>();
}
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
/**
* Gets one of this object's properties
* using the associated key.
*
* @see #putValue
*/
public Object getValue(String key) {
return ama.getValue(key);
}
/**
* Sets one of this object's properties
* using the associated key. If the value has
* changed, a <code>PropertyChangeEvent</code> is sent
* to listeners.
*
* @param key a <code>String</code> containing the key
* @param value an <code>Object</code> value
*/
public void putValue(String key, Object value) {
ama.putValue(key, value);
}
/**
* Sets the enabled state of the <code>Action</code>. When enabled,
* any component associated with this object is active and
* able to fire this object's <code>actionPerformed</code> method.
* If the value has changed, a <code>PropertyChangeEvent</code> is sent
* to listeners.
*
* @param b true to enable this <code>Action</code>, false to disable it
*/
public void setEnabled(boolean b) {
ama.setEnabled(b);
}
/**
* Returns the enabled state of the <code>Action</code>. When enabled,
* any component associated with this object is active and
* able to fire this object's <code>actionPerformed</code> method.
*
* @return true if this <code>Action</code> is enabled
*/
public boolean isEnabled() {
return ama.isEnabled();
}
/**
* Removes a <code>PropertyChange</code> listener.
*
* @param listener a <code>PropertyChangeListener</code> object
* @see #addPropertyChangeListener
*/
public void removePropertyChangeListener(PropertyChangeListener listener) {
if (listeners != null) {
listeners.remove(listener);
if (listeners.size() == 0) {
listeners = null;
}
}
}
/**
* Invoked when an action occurs.
*/
public void actionPerformed(ActionEvent e) {
ama.actionPerformed(e.getSource());
}
/**
* Tell the listener that a property has changed.
*
* @param propertyName String name of property
* @param oldValue Object value before the change
* @param newValue Object value after the change
*/
public void menuPropertyChanged(String propertyName, Object oldValue, Object newValue) {
ArrayList<PropertyChangeListener> listeners;
if ((listeners = this.listeners) != null) { // avoid getfield code and possible NPE
PropertyChangeEvent pce = new PropertyChangeEvent(this, propertyName, oldValue, newValue);
for (int i = listeners.size() - 1; i >= 0; i--) {
listeners.get(i).propertyChange(pce);
}
}
}
/**
* DO NOT CALL. Handles association of JMenuItemCheckBox with MenuAction.
* @param e ItemEvent reporting JMenuItemCheckBox state change
*/
public void itemStateChanged(ItemEvent e) {
ama.actionPerformed(e.getSource());
}
}

View File

@ -0,0 +1,898 @@
package org.ka2ddo.yaac.io;
/*
* Copyright (C) 2011-2021 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import org.ka2ddo.ax25.AX25Message;
import org.ka2ddo.ax25.Connector;
import org.ka2ddo.yaac.util.Localizer;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
/**
* This class contains all the parameters for configuring a PortConnector in
* this application, and the encoding and decoding code for saving its value
* in the persisted configuration data.
* @author Andrew Pavlin, KA2DDO
*/
public class PortConfig implements Serializable, Comparable<PortConfig> {
private static final long serialVersionUID = -162577201270002899L;
/**
* Empty array for ports that have no supported aliases.
* @see Cfg#digiAliases
*/
public static final String[] NO_ALIASES = new String[0];
/**
* Flag bit indicating this port allows transmitting APRS packets.
* @see Cfg#acceptableProtocolsMask
*/
public static final int PROTOCOL_APRS = 1;
/**
* Flag bit indicating this port allows transmitting OpenTRAC packets.
* @see Cfg#acceptableProtocolsMask
*/
public static final int PROTOCOL_OPENTRAC = 2;
/**
* Flag bit indicating this port allows transmitting AX.25 frames containing traffic of types
* other than APRS or OpenTRAC UI frames.
* @see #PROTOCOL_APRS
* @see #PROTOCOL_OPENTRAC
* @see Cfg#acceptableProtocolsMask
*/
public static final int PROTOCOL_AX25 = 4;
/**
* Highest bit number supported in protocol bit mask.
* @see Cfg#acceptableProtocolsMask
*/
public static final int MAX_PROTOCOL_BIT = 2;
/**
* No waypoint sentences sent to GPS on this port.
*/
public static final int SENTENCE_NONE = 0;
/**
* Send NMEA standard waypoint message back to GPS.
*/
public static final int SENTENCE_GPWPL = 1;
/**
* Send Kenwood-specific waypoint message back to GPS.
*/
public static final int SENTENCE_PKWDWPL = 2;
/**
* Indicates this is a TNC port over HF (low bandwidth, wide geographic coverage).
* @see Cfg#flags
*/
public static final int FLAGS_HF = 1;
/**
* Indicates this is a UDP socket (not TCP).
* @see Cfg#flags
*/
public static final int FLAGS_UDP = 2;
/**
* Indicates that this data should be consumed locally as well as transmitted.
* @see Cfg#flags
*/
public static final int FLAGS_LOCAL = 4;
/**
* Indicates that non-flow-control serial ports should not raise the DTR and RTS signals.
* @see Cfg#flags
*/
public static final int FLAGS_NODTR = 8;
/**
* Indicates that port opening should always retry if it fails, even on initial opening.
* Meant for APRS-IS port driver when used with flaky and/or intermittent Internet connection.
* @see Cfg#flags
*/
public static final int FLAGS_ALWAYS_RETRY = 16;
/**
* Indicates that work-around for strange device features should be used on this port.
* For example, the KISS protocol ports use this to control the work-around for Kenwood
* APRS/TNC radios that could interpret certain characters in a KISS packet as commands
* from the memory programming application and thereby screw up the radio's settings. However,
* other TNCs do not accept the work-around.
* @see Cfg#flags
*/
public static final int FLAGS_USE_WORKAROUND = 32;
/**
* Indicates that this port does not require the port driver to wait for the external
* device to finish processing the last outbound traffic before sending more traffic.
* This is for ports such as Serial_TNC, which could be connected to quarter-duplex devices
* like old TNCs that can't handle receiving another outbound packet from the computer
* while still transmitting the last packet to RF.
* @see Cfg#flags
*/
public static final int FLAGS_PIPELINED = 64;
/**
* When using timeslotting on a TNC port, don't coalesce duplicate packets (such as beacons or
* status frames). This is useful for cases such as meteor-scatter operation. Not all TNC port types
* may support this capability. Note this is coded as a negative value to preserve backwards compatibility
* with YAAC installations configured at an earlier build.
* @see Cfg#flags
*/
public static final int FLAGS_DONT_COALESCE = 128;
/**
* These four bits contain the KISS device ID to be used in KISS frames sent through this port.
* This supports the {@link KissOverTcpConnector} port type when talking to the DireWolf
* software TNC which can support up to 6 audio devices (and therefore up to 6 device IDs in
* KISS frames). Conveniently, since these bits weren't used before, the backwards-compatible
* default KISS device ID is zero.
* @see Cfg#flags
*/
public static final int FLAGS_MASK_KISSPORT = 0xF00;
/**
* This constant gets the number of bits to shift the above {@link #FLAGS_MASK_KISSPORT} bits right
* to put them in the least significant bits of an integer value.
* @see Cfg#flags
*/
public static final int FLAGS_SHIFT_KISSPORT = 8;
/**
* When looking up a TCP/IP network object, the {@link Cfg#deviceName deviceName} is not a fully-qualified domain name (FQDN)
* or numeric IP address of the target, but rather a service instance name for some service supported
* by the port per Internet RFC 6763 (DNS-Based Service Discovery), such that the code needs to do a
* service discovery to get the correct host name prior to using DNS to translate the host name.
* @see Cfg#flags
*/
public static final int FLAGS_IS_SRV_INST_NAME = 0x1000;
/**
* Indicate that {@link Cfg#deviceName} is of a hardware device rather than
* some software concept (like a network port).
*/
public static final int FLAGS_HARDWARE = 0x2000;
/**
* Enumeration identifying all the fields in a Cfg sub-record. Used by port driver classes to support
* configuration transfer by XML to indicate which fields are required and also which are to be suppressed (to
* block duplicate station callsigns, etc.).
* @see PortConnector
* @see ConfigImporter
* @author Andrew Pavlin, KA2DDO
*/
public enum Fields {
/** @see Cfg#enabled */
enabled,
/** @see Cfg#deviceName */
deviceName,
/** @see Cfg#baudRate */
baudRate,
/** @see Cfg#callsign */
callsign,
/** @see Cfg#passcode */
passcode,
/** @see Cfg#filter */
filter,
/** @see Cfg#transmitAllowed */
transmitAllowed,
/** @see Cfg#flowControlled */
flowControlled,
/** @see Cfg#flags */
flags,
/** @see Cfg#digiAliases */
digiAliases,
/** @see Cfg#acceptableProtocolsMask */
acceptableProtocolsMask,
/** @see Cfg#timeslotCycleLength */
timeslotCycleLength,
/** @see Cfg#timeslotOffset */
timeslotOffset,
/** @see Cfg#beaconNames */
beaconNames,
/** @see Cfg#timeslotLength */
timeslotLength }
/**
* For the {@link ConfigImporter}, provide hints of how required but non-transferable config fields
* should be asked for.
* @author Andrew Pavlin, KA2DDO
* @see RequireHints
*/
public enum HintType {
/**
* Just prompt with a plain text edit field.
*/
TEXT,
/**
* Just prompt with a plain text edit field, but only accept uppercase.
*/
TEXT_UC,
/**
* Prompt with a combo box pre-loaded with available serial ports.
*/
SERIAL_PORTS,
/**
* Prompt with a combo box pre-loaded with standard APRS-IS Tier 2 rotator hostnames.
*/
APRSIS_HOSTS,
/**
* Prompt with a combo box pre-loaded with the localhost name.
*/
LOCALHOST,
/**
* Prompt with a file chooser. {@link RequireHints} for this type must be a {@link RequireFile}.
*/
FILE_CHOOSER,
/**
* Prompt with a combo box loaded with a String array obtained from a static
* getDeviceNames() method provided by the {@link PortConnector} subclass.
*/
GENERIC_CALL_FOR_NAMELIST,
/**
* Just prompt with a plain text edit field, but only accept characters legal in a callsign-SSID.
*/
CALLSIGN_SSID
}
/**
* Data structure describing how the ConfigImporter should ask for missing (or
* blanked-out) required port configuration parameters.
* @author Andrew Pavlin, KA2DDO
* @see ConfigImporter
*/
public static class RequireHints {
/**
* The ResourceBundle key for a localized prompt string to display in a dialog box.
*/
public final String promptTag;
/**
* The data entry field type to be used in the prompting dialog box.
*/
public final HintType typeOfDialogPrompt;
/**
* Create a RequireHints object for the specified prompt string and a default text field.
* @param promptTag String key to a ResourceBundle localized prompt text
*/
public RequireHints(String promptTag) {
this.promptTag = promptTag;
this.typeOfDialogPrompt = HintType.TEXT;
}
/**
* Create a RequireHints object for the specified prompt string and the specified type
* of data entry field.
* @param promptTag String key to a ResourceBundle localized prompt text
* @param typeOfDialogPrompt HintType enum for the type of data entry to use
*/
public RequireHints(String promptTag, HintType typeOfDialogPrompt) {
this.promptTag = promptTag;
this.typeOfDialogPrompt = typeOfDialogPrompt;
}
}
/**
* Data structure describing how the ConfigImporter should ask for missing (or
* blanked-out) required port configuration parameters when they are a file in the
* filesystem.
* @author Andrew Pavlin, KA2DDO
* @see ConfigImporter
* @see HintType#FILE_CHOOSER
*/
public static class RequireFile extends RequireHints {
/**
* ResourceBundle key to localized text to display on file chooser's select button.
*/
public final String fileSelectTag;
/**
* Boolean true indicates chooser should select a directory, false for files.
*/
public final boolean isDirectory;
/**
* Create a RequireFile object for the specified prompt string and a file chooser.
* @param promptTag String key to a ResourceBundle localized prompt text
* @param fileSelectTag String text to a ResourceBundle localized text to display on the file chooser's select button
* @param isDirectory boolean true if user must select directory, false for file
*/
public RequireFile(String promptTag, String fileSelectTag, boolean isDirectory) {
super(promptTag, HintType.FILE_CHOOSER);
this.fileSelectTag = fileSelectTag;
this.isDirectory = isDirectory;
}
}
private String displayName = "";
private int portNumber = -1;
/**
* Type of PortConnector used to implement this port, generally defined as a String constant
* named PORT_TYPE on the PortConnector subclass.
*/
public String portType = "";
/**
* Whether port should be opened automatically upon startup.
*/
public boolean enabled = true;
/**
* Port-type-specific configuration parameters for a PortConnector.
* @author Andrew Pavlin, KA2DDO
*/
public static class Cfg implements Serializable, Comparable<Cfg> {
private static final long serialVersionUID = 885437494458611609L;
/**
* The device name or network host name/address associated with this port.
*/
public String deviceName = "";
/**
* The baud rate used for the port, if needed, or port number for TCP and UDP socket connections.
*/
public int baudRate = 0;
/**
* The amateur radio station callsign associated with this port, if needed.
* Also used for weather station model name.
*/
public String callsign = "";
/**
* The authentication passcode associated with this port, if needed.
*/
public String passcode = "";
/**
* Any filter expression associated with this port, if needed.
*/
public String filter = "";
/**
* Indicates whether messages can be transmitted from YAAC via this port.
*/
public boolean transmitAllowed;
/**
* Indicates whether flow control is enabled on this port.
*/
public boolean flowControlled;
/**
* A collection of flag bits indicating other attributes of the port configuration.
* @see #FLAGS_HF
* @see #FLAGS_UDP
* @see #FLAGS_LOCAL
* @see #FLAGS_NODTR
* @see #FLAGS_ALWAYS_RETRY
* @see #FLAGS_USE_WORKAROUND
* @see #FLAGS_PIPELINED
* @see #FLAGS_DONT_COALESCE
* @see #FLAGS_MASK_KISSPORT
* @see #FLAGS_IS_SRV_INST_NAME
* @see #FLAGS_HARDWARE
*/
public int flags;
/**
* Array of digipeat alias Strings for which this port will digipeat (if transmitAllowed is true).
*
* @see #transmitAllowed
*/
public String[] digiAliases = NO_ALIASES;
/**
* Bit mask or enum number of protocols that can be transmitted through this port. Only meaningful for
* ports with CAP_XMT_PACKET_DATA set (for protocol bitmask) or CAP_WAYPOINT_SENDER (for enum).
* @see Connector#CAP_XMT_PACKET_DATA
* @see Connector#CAP_WAYPOINT_SENDER
* @see #PROTOCOL_APRS
* @see #PROTOCOL_OPENTRAC
* @see #PROTOCOL_AX25
* @see #SENTENCE_NONE
* @see #SENTENCE_GPWPL
* @see #SENTENCE_PKWDWPL
*/
public int acceptableProtocolsMask = PROTOCOL_APRS;
/**
* Number of seconds in a timeslot cycle, which will be aligned to UTC and the Unix epoch time
* (if some weird prime number is used). Negative values means timeslotting is disabled, but is
* preserving the last-used cycle length in case it gets re-enabled.
*/
public int timeslotCycleLength = -120;
/**
* Number of seconds since the start of a cycle when this port is allowed to transmit.
* This should be an integer multiple of {@link #timeslotLength} greater than or equal to
* zero, but less than the {@link #timeslotCycleLength}.
*/
public int timeslotOffset = 0;
/**
* Number of seconds in a timeslot. This should be a positive integer fraction of the
* {@link #timeslotCycleLength}. Zero or negative means that once a station enters its
* timeslot, it can transmit until its queue for the port is empty, rather than having
* to stop because another station's timeslot has started.
*/
public int timeslotLength = 10;
/**
* Names of beacon instances to send through this port (zero-length array means only default beacon).
* @see BeaconData
* @see BeaconData#MYCALL
* @see BeaconData#beaconName
*/
public String[] beaconNames = new String[0];
/**
* Make a deep copy of this Cfg object.
*
* @return duplicate Cfg
*/
public Cfg dup() {
Cfg cfg = new Cfg();
cfg.deviceName = deviceName;
cfg.baudRate = baudRate;
cfg.callsign = callsign;
cfg.passcode = passcode;
cfg.transmitAllowed = transmitAllowed;
cfg.digiAliases = new String[digiAliases.length];
System.arraycopy(digiAliases, 0, cfg.digiAliases, 0, digiAliases.length);
cfg.filter = filter;
cfg.flowControlled = flowControlled;
cfg.acceptableProtocolsMask = acceptableProtocolsMask;
cfg.timeslotCycleLength = timeslotCycleLength;
cfg.timeslotOffset = timeslotOffset;
cfg.timeslotLength = timeslotLength;
cfg.flags = flags;
cfg.beaconNames = Arrays.copyOf(beaconNames, beaconNames.length);
return cfg;
}
/**
* Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.
*
* @param o the object to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
*
* @throws ClassCastException if the specified object's type prevents it
* from being compared to this object.
*/
public int compareTo(Cfg o) {
return deviceName.compareTo(o.deviceName);
}
/**
* Indicates whether some other object is "equal to" this one.
*
* @param obj the reference object with which to compare.
* @return {@code true} if this object is the same as the obj
* argument; {@code false} otherwise.
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Cfg) {
Cfg other = (Cfg)obj;
return other.deviceName.equals(deviceName) &&
other.baudRate == baudRate &&
other.callsign.equals(callsign) &&
other.passcode.equals(passcode) &&
other.transmitAllowed == transmitAllowed &&
Arrays.equals(digiAliases, other.digiAliases) &&
other.filter.equals(filter) &&
other.flowControlled == flowControlled &&
other.acceptableProtocolsMask == acceptableProtocolsMask &&
other.timeslotCycleLength == timeslotCycleLength &&
other.timeslotOffset == timeslotOffset &&
other.timeslotLength == timeslotLength &&
other.flags == flags &&
Arrays.equals(other.beaconNames, beaconNames);
}
return false;
}
/**
* Returns a hash code value for the object.
*
* @return a hash code value for this object.
* @see #equals(Object)
*/
@Override
public int hashCode() {
return deviceName.hashCode();
}
/**
* Returns a string representation of the object.
* @return a string representation of the object.
*/
@Override
public String toString() {
return "PortConfig.Cfg[" + deviceName + ',' + baudRate + ',' + callsign + ',' + (transmitAllowed ? "xmt" : "") +
",digi=" + Arrays.toString(digiAliases) + ',' + (null != filter && filter.length() > 0 ? "filt=\"" + filter + '"' : "") +
(flags != 0 ? ",flags=0x" + Integer.toHexString(flags) : "") +
(timeslotCycleLength > 0 ? ",ts=" + timeslotOffset + '(' + timeslotLength + ")/" + timeslotCycleLength : "") + ']';
}
}
private final HashMap<String, Cfg> variations = new HashMap<String, Cfg>(2);
/**
* Get the port-type-specific configuration parameters for the currently specified port type.
* @return type-specific Cfg record
*/
public final Cfg current() {
Cfg cfg;
if ((cfg = variations.get(portType)) == null) {
variations.put(portType, cfg = new Cfg());
}
return cfg;
}
/**
* Get the port-type-specific configuration parameters for a specific port type.
* @param portType the name of the port type whose configuration parameters should be obtained
* @return type-specific Cfg record (created if it did not previously exist)
*/
public final Cfg specific(String portType) {
Cfg cfg;
if ((cfg = variations.get(portType)) == null) {
variations.put(portType, cfg = new Cfg());
}
return cfg;
}
/**
* Create a new PortConfig record with a new port instance identifier.
*/
public PortConfig() {}
/**
* Allocate a new port number for this PortConfig object.
*/
public void assignPortNumber() {
portNumber = PortManager.getNextAvailablePortNumber();
displayName = "Port" + portNumber;
}
/**
* Craete a PortConfig record with the specified port instance identifier.
* @param displayName port identifier name to use
*/
public PortConfig(String displayName) {
this.displayName = displayName;
portNumber = Integer.parseInt(displayName.substring(4).trim());
}
/**
* Indicates whether some other object is "equal to" this one.
*
* @param obj the reference object with which to compare.
* @return <code>true</code> if this object is the same as the obj
* argument; <code>false</code> otherwise.
* @see #hashCode()
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof PortConfig) {
PortConfig o = (PortConfig)obj;
return displayName.equals(o.displayName);
}
return false;
}
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hashtables such as those provided by
* <code>java.util.Hashtable</code>.
*
* @return a hash code value for this object.
* @see #equals(Object)
*/
@Override
public int hashCode() {
return displayName.hashCode();
}
/**
* Get the name by which this port will be identified.
* @return String port name
*/
public String getDisplayName() {
return displayName;
}
/**
* Get the arbitrary sequence port number assigned to this configuration record.
* @return port number
*/
public int getPortNumber() {
return portNumber;
}
/**
* Returns a string representation of the object.
*
* @return a string representation of the object.
*/
@Override
public String toString() {
Cfg cfg = current();
return "PortConfig[" + displayName + '>' + portType + ',' + cfg.deviceName + ',' + cfg.baudRate + ',' + cfg.callsign + ']';
}
/**
* Make a deep copy of this PortConfig object.
* @return duplicate PortConfig
*/
public PortConfig dup() {
PortConfig cfg = new PortConfig(displayName);
cfg.portType = portType;
cfg.enabled = enabled;
for (Map.Entry<String, Cfg> entry : variations.entrySet()) {
cfg.variations.put(entry.getKey(), entry.getValue().dup());
}
return cfg;
}
/**
* Do a deep copy of this PortConfig object into another object..
* @param dest destination PortConfig
*/
public void copyInto(PortConfig dest) {
dest.portType = portType;
dest.enabled = enabled;
dest.variations.clear();
dest.variations.put(portType, variations.get(portType));
}
/**
* Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.
*
* @param other the object to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
*
* @throws ClassCastException if the specified object's type prevents it
* from being compared to this object.
*/
public int compareTo(PortConfig other) {
int diff = portType.compareTo(other.portType);
if (0 == diff) {
diff = current().compareTo(other.current());
}
return diff;
}
/**
* Write this PortConfig object to Java Preferences on the Ports sub-node of the
* specified Preferences node.
* @param root Preferences node to use to store the Ports sub-node
*/
public void writeToPreferences(Preferences root) {
// write or overwrite preferences
Preferences portNode = root.node("Ports");
portNode.put(displayName, generatePrefsString());
try {
portNode.flush();
} catch (BackingStoreException e) {
e.printStackTrace(System.out);
}
}
/**
* <p>Generate the encoded String for storing the PortConfig into Java Preferences.
* The preferences String consists of multiple expressions separated by semicolon ";"
* characters. The first expression (required) will have the following format:</p>
* <code><i>portType</i>,<i>deviceName</i>,<i>baudRateOrPortNumber</i>,<i>callsign</i>,<i>passcode</i>,<i>transmitEnabled</i>[,<i>digipeatAlias[,...]]</i></code>
* <p>where <i>deviceName</i> and <i>passcode</i> are escaped to be legitimate text in an XML tag body,
* <i>transmitEnabled</i> is either "true" or "false", and there can be any number of digipeat aliases
* in the syntax of display-format AX.25 callsign-SSID values.</p>
* <p>The second expression is a APRS-IS filter expression.</p>
* <p>The third expression is "true" or "false" for whether this port is enabled for operation.</p>
* <p>The fourth expression is "true" or "false" for whether flow control (or something overloading
* the meaning of this field) should be enabled or not.</p>
* <p>The fifth expression is a decimal number of the value of the {@link Cfg#acceptableProtocolsMask} bitmask.</p>
* <p>The sixth expression is two decimal numbers separated by a slash "/" character representing
* the timeslot offset (relative to the top of the cycle) in seconds and the length of the timeslot cycle in seconds.
* Negative cycle length indicates that timeslotting is not used, but the values are preserved
* in case the user wants to turn timeslotting back on.</p>
* <p>The seventh expression is a decimal number of the value of the {@link Cfg#flags} bitmask.
* Bit meanings are portType-specific.</p>
* <p>The eighth expression is a pipe "|" separated list of names of {@link BeaconData}
* definitions that should be transmitted through this port. Only meaningful for ports
* capable of transmitting APRS or OpenTRAC packets.</p>
* @return encoded String
* @see #decodePreferenceValue(String)
*/
public String generatePrefsString() {
Cfg cfg = current();
StringBuilder b = new StringBuilder(portType).append(',').append(passcodeEncode(cfg.deviceName)).append(',').
append(cfg.baudRate).append(',').append(cfg.callsign).append(',').append(passcodeEncode(cfg.passcode)).append(',').append(cfg.transmitAllowed);
for (String r : cfg.digiAliases) {
b.append(',').append(r);
}
b.append(';').append(cfg.filter).append(';').append(enabled).append(';').append(cfg.flowControlled).append(';').append(cfg.acceptableProtocolsMask);
b.append(';').append(cfg.timeslotOffset).append('/').append(cfg.timeslotCycleLength).append('/').append(cfg.timeslotLength);
b.append(';').append(cfg.flags).append(';');
for (int i = 0; i < cfg.beaconNames.length; i++) {
if (0 != i) {
b.append('|');
}
b.append(cfg.beaconNames[i]);
}
return b.toString();
}
/**
* Encode the passcode so characters that would break XML or our field concatenation are escaped.
* @param passcode String containing passcode
* @return escaped passcode
*/
private static String passcodeEncode(String passcode) {
boolean needsEscape = false;
int len = passcode.length();
for (int i = 0; i < len; i++) {
char ch = passcode.charAt(i);
if (ch < ' ' || ch >= '\u007F' || '&' == ch || '"' == ch || '\\' == ch || ',' == ch || ';' == ch) {
needsEscape = true;
break;
}
}
if (needsEscape) {
StringBuilder b = new StringBuilder(len + 20);
for (int i = 0; i < len; i++) {
char ch = passcode.charAt(i);
if (ch < ' ' || ch >= '\u007F' || '&' == ch || '"' == ch || '\\' == ch || ',' == ch || ';' == ch) {
b.append("\\u").append(Integer.toHexString(0x10000 + (int)ch).substring(1));
} else {
b.append(ch);
}
}
passcode = b.toString();
}
return passcode;
}
/**
* Delete this PortConfig from the Java Preferences backing store.
* @param root Preferences node used to store the Ports sub-node
*/
public void removeFromPreferences(Preferences root) {
// remove this PortConfig from the Preferences
Preferences portNode = root.node("Ports");
portNode.remove(displayName);
}
/**
* Read a PortConfig object from Java Preferences.
* @param root Preferences node used to store the Ports sub-node
* @param displayName the String port identifier name to read
* @return a populated PortConfig object for the specified name, or null if no such record
* exists in the specified node of Preferences
*/
public static PortConfig readFromPreferences(Preferences root, String displayName) {
PortConfig config = null;
Preferences portNode = root.node("Ports");
String values = portNode.get(displayName, null);
if (values != null) {
config = new PortConfig(displayName);
config.decodePreferenceValue(values);
}
return config;
}
/**
* Decode the storage format used to store a PortConfig in Java Preferences into
* this PortConfig object.
* @param values String of encoded values
* @see #generatePrefsString()
*/
public void decodePreferenceValue(String values) {
String[] prePostFilter = AX25Message.split(values, ';');
String[] tk = AX25Message.split(prePostFilter[0], ',');
portType = tk[0];
if (portType.equals("I-Gate")) {
portType = "APRS-IS";
}
Cfg cfg = current();
cfg.deviceName = passcodeDecode(tk[1]);
if (tk.length >= 3) {
cfg.baudRate = Integer.parseInt(tk[2]);
if (tk.length >= 4) {
cfg.callsign = tk[3];
if (tk.length >= 5) {
cfg.passcode = passcodeDecode(tk[4]);
if (tk.length >= 6) {
cfg.transmitAllowed = Boolean.parseBoolean(tk[5]);
if (tk.length >= 7) {
cfg.digiAliases = new String[tk.length - 6];
System.arraycopy(tk, 6, cfg.digiAliases, 0, cfg.digiAliases.length);
}
}
}
}
}
if (prePostFilter.length > 1) {
cfg.filter = prePostFilter[1];
if (prePostFilter.length > 2) {
if (prePostFilter[2].length() > 0) {
enabled = Boolean.parseBoolean(prePostFilter[2]);
}
if (prePostFilter.length > 3) {
if (prePostFilter[3].length() > 0) {
cfg.flowControlled = Boolean.parseBoolean(prePostFilter[3]);
}
if (prePostFilter.length > 4) {
if (prePostFilter[4].length() > 0) {
cfg.acceptableProtocolsMask = Integer.parseInt(prePostFilter[4]);
}
if (prePostFilter.length > 5) {
if (prePostFilter[5].length() > 0) {
String[] timeslotParams = AX25Message.split(prePostFilter[5], '/');
cfg.timeslotOffset = Integer.parseInt(timeslotParams[0]);
cfg.timeslotCycleLength = Integer.parseInt(timeslotParams[1]);
if (timeslotParams.length >= 3) {
cfg.timeslotLength = Integer.parseInt(timeslotParams[2]);
} else {
cfg.timeslotLength = 0; // for backwards compatibility
}
}
if (prePostFilter.length > 6) {
if (prePostFilter[6].length() > 0) {
cfg.flags = Integer.parseInt(prePostFilter[6]);
}
if (prePostFilter.length > 7) {
if (prePostFilter[7].length() > 0) {
cfg.beaconNames = AX25Message.split(prePostFilter[7], '\u001E');
//TODO: temporary repair until damage from build#107 is removed
String defaultBeaconDisplayName = Localizer.getMsg("configure.tab.Beacon.DefaultBeaconName");
for (int i = 0; i < cfg.beaconNames.length; i++) {
if (cfg.beaconNames[i].equals(defaultBeaconDisplayName)) {
cfg.beaconNames[i] = BeaconData.MYCALL;
}
}
} else {
cfg.beaconNames = new String[0];
}
}
}
}
}
}
}
}
}
/**
* Decode the passcode so characters that would break XML or our field concatenation are un-escaped.
* @param passcode String containing escaped passcode
* @return unescaped passcode
*/
private static String passcodeDecode(String passcode) {
boolean hasEscape = false;
int len = passcode.length();
for (int i = 0; i < len; i++) {
char ch = passcode.charAt(i);
if ('\\' == ch) {
hasEscape = true;
break;
}
}
if (hasEscape) {
StringBuilder b = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char ch = passcode.charAt(i);
if ('\\' == ch) {
if (passcode.charAt(i+1) != 'u' || i + 6 > len) {
throw new IllegalArgumentException("invalid encoded string \"" + passcode + '"');
}
ch = (char)Integer.parseInt(passcode.substring(i+2,i+6), 16);
i += 5;
}
b.append(ch);
}
passcode = b.toString();
}
return passcode;
}
}

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>RXTXcomm</groupId>
<artifactId>RXTXcomm</artifactId>
<version>2.2pre2</version>
<name>RXTXcomm</name>
<description>
RXTXcomm implements the Java Serial Communications API with native libraries for Microsoft Windows (32-bit and 64-bit),
Linux (i386 and x86_64 architectures), and Macintosh OS X on Intel hardware.
</description>
<url>http://rxtx.qbang.org</url>
<scm>
<connection>cvs:pserver:anonymous@qbang.org:/var/cvs/cvsroot</connection>
<developerConnection>scm:cvs:pserver:anonymous@qbang.org:/var/cvs/cvsroot</developerConnection>
</scm>
<dependencies>
<!-- used for unit tests -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compile.source>1.4</maven.compile.source>
<maven.compile.target>1.4</maven.compile.target>
<commons.componentid>compress</commons.componentid>
<commons.release.version>1.4</commons.release.version>
<commons.release.name>RXTXcomm-2.2pre2</commons.release.name>
<commons.osgi.symbolicName>gnu.io</commons.osgi.symbolicName>
<commons.deployment.protocol>cvs</commons.deployment.protocol>
<commons.binary.suffix />
</properties>
</project>

View File

@ -0,0 +1,112 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Querying Stations</title>
</head>
<body lang="en-US" dir="LTR">
<h1>Querying Stations</h1>
<p>There are several types of queries one transmit-capable APRS station can make to
another. The most common, requesting an updated position report, is
automatically issued on a <a href="./locatestation.html">Locate-&gt;Station</a> request if YAAC doesn't have
recent position information about the requested station. Other common
queries supported by YAAC are:</p>
<ul>
<li>Query-&gt;Station Direct: The list of stations heard directly (not via digipeaters or
IGates) by the queried station
<li>Query-&gt;Station Heard: How many messages have been heard by the queried station from
a third station
<li>Query-&gt;Station Ping and Query-&gt;Station Trace: The path from the queried station to the querying station
(using either the ?PING? or ?APRST queries)
<li>Query-&gt;Station Status: What is the remote station's status message?</li>
<li>Query-&gt;Station SW Version: a description of the software being executed on the queried station.
Note this query is presently supported only by YAAC, and KJ4ERJ's APRSIS/32 and APRSIS/CE, and will probably
be ignored by other stations.</li>
</ul>
<p><b>NOTE</b> None of these commands will be available until after you have <a href="./FAQ.html#noquery">set up a
transmit-capable APRS port</a> in YAAC. You may need to restart YAAC so that the menu bar will notice
the new capability and add the extra menu options.</p>
<p>In addition, YAAC can make broadcast queries to search for RF-local stations, either any APRS station, or
those performing I-gate or weather station functions. These broadcast queries can only be used when YAAC
has a RF connection and will only be transmitted out RF ports.</p>
<p>Also, YAAC can broadcast <a name="queryQRU">QRU requests</a> to find out about local resources of various types.
Some of the standard QRU groups that are commonly used are:</p>
<table border="1"><tbody>
<tr><th>group name</th><th>meaning</th></tr>
<tr><td>AMBU</td><td>ambulance</td></tr>
<tr><td>CLUB</td><td>ham radio club</td></tr>
<tr><td>ECHO</td><td>Echolink</td></tr>
<tr><td>FIRE</td><td>fire station</td></tr>
<tr><td>FOOD</td><td>restaurants</td></tr>
<tr><td>FUEL</td><td>gas/petrol stations</td></tr>
<tr><td>HOSP</td><td>hospitals</td></tr>
<tr><td>LIFEBOAT</td><td>lifeboats</td></tr>
<tr><td>LTHS</td><td>lighthouses</td></tr>
<tr><td>POLI</td><td>police stations</td></tr>
<tr><td>POST</td><td>post offices</td></tr>
<tr><td>RD13</td><td>D-Star 13cm repeaters</td></tr>
<tr><td>RD23</td><td>D-Star 23cm repeaters</td></tr>
<tr><td>RD2M</td><td>D-Star 2m repeaters</td></tr>
<tr><td>RD3C</td><td>D-Star 3cm repeaters</td></tr>
<tr><td>RD70</td><td>D-Star 70cm repeaters</td></tr>
<tr><td>RP10</td><td>analog 10m repeaters</td></tr>
<tr><td>RP13</td><td>analog 13cm repeaters</td></tr>
<tr><td>RP23</td><td>analog 23cm repeaters</td></tr>
<tr><td>RP2M</td><td>analog 2m repeaters</td></tr>
<tr><td>RP3C</td><td>analog 3cm repeaters</td></tr>
<tr><td>RP6M</td><td>analog 6m repeaters</td></tr>
<tr><td>RP70</td><td>analog 70cm repeaters</td></tr>
<tr><td>RT13</td><td>television 13cm repeaters</td></tr>
<tr><td>RT23</td><td>television 23cm repeaters</td></tr>
<tr><td>RT3C</td><td>television 3cm repeaters</td></tr>
<tr><td>SRAIL</td><td>steam railroad</td></tr>
<tr><td>STOR</td><td>Amateur Radio stores</td></tr>
<tr><td>T2SRV</td><td>approximate locations of Tier 2 APRS-IS servers</td></tr>
<tr><td>VETE</td><td>veterinarians</td></tr>
<tr><td>WOTA</td><td>Wainwrights On The Air peaks</td></tr>
</tbody></table>
<p>To find out what QRU codes are in use in your area, send the default query INFO; every QRU server station in range will
report the QRU codes they serve and their maximum range.</p>
<p>YAAC can also function as a QRU server, based on the default beacon position. Objects and Items announced by
YAAC can be <a href="./objecteditor.html#defineQRU">associated with a QRU group</a>, and will be automatically retransmitted when a corresponding in-range
QRU request is received.</p>
<p>YAAC can also send and receive lists of <a name="tactical">tactical callsigns (aliases)</a> in use by RF-local
stations and objects; this message interchange is compatible with Xastir and APRS+SA.</p>
<p>The APRS-standard and custom queries that YAAC will respond to are:</p>
<table border="1"><tbody>
<tr><th>Directed query code</th><th>Response</th></tr>
<tr><td>??</td><td>This list of query commands recognized by YAAC.</td></tr>
<tr><td>?ABOUT</td><td>The version of YAAC software being used.</td></tr>
<tr><td>?ALOHA</td><td>The current radius of the Aloha circle for this YAAC station.</td></tr>
<tr><td>?APRSD</td><td>The list of directly heard stations.</td></tr>
<tr><td>?APRSH <I>callsign</I></td><td>The number of messages heard from the specified station for each of the last 8 hours.</td></tr>
<tr><td>?APRSL</td><td>The list of stations considered local for I-Gating by this station.</td></tr>
<tr><td>?APRSM</td><td>Immediately retransmit all unacknowledged messages transmitted to the requesting station.</td></tr>
<tr><td>?APRSO</td><td>Immediately transmit all objects and items controlled by this station.</td></tr>
<tr><td>?APRSP</td><td>Immediately transmit the current position of this station.</td></tr>
<tr><td>?APRSS</td><td>Immediately transmit the current status message of this station.</td></tr>
<tr><td>?APRST</td><td>A message with the path from the requesting station to the queried station.</td></tr>
<tr><td>?APRSV</td><td>The version of YAAC software being used.</td></tr>
<tr><td>?IGATE</td><td>If this station is functioning as an I-Gate, the I-Gate statistics for this station.</td></tr>
<tr><td>?PING?</td><td>A message with the path from the requesting station to the queried station.</td></tr>
<tr><td>?QUERIES</td><td>This list of query commands recognized by YAAC.</td></tr>
<tr><td>?UP</td><td>Immediately transmit the duration of time this instance of YAAC has been running.</td></tr>
<tr><td>?VER</td><td>The version of software being used.</td></tr>
<tr><td>?WX</td><td>If this station has a weather station attached, immediately send the station position and a positionless weather message.</td></tr>
<tr><td>?YDBG</td><td>Report debugging information about the local YAAC station, including memory consumption, station/object quantity being monitored, current map pan and zoom, and thread status.</td></tr>
</tbody></table>
<a name="broadcast"><table border="1"><tbody>
<tr><th>Broadcast query code</th><th>Response</th></tr>
<tr><td>?APRS?</td><td>Immediately transmit the current position of this station.</td></tr>
<tr><td>?IGATE?</td><td>If this station is functioning as an I-Gate, the I-Gate statistics for this station.</td></tr>
<tr><td>?WX?</td><td>If this station has a weather station attached, immediately send the station position and a positionless weather message.</td></tr>
</tbody></table></a>
<p>Note that the above broadcasts are only permitted on RF, as every station on the planet would be expected to
respond if these were sent over the APRS-IS Internet servers.</p>
<table border="1"><tbody>
<tr><th>Broadcast message destination</th><th>Meaning</th></tr>
<tr><td>QRU</td><td>ask for local resources</td></tr>
<tr><td>TACTICAL</td><td>send or receive tactical callsign aliases for real callsign-SSIDs</td></tr>
</tbody></table>
</body>
</html>

View File

@ -0,0 +1,716 @@
package org.ka2ddo.yaac.legacyrxtx.io;
/*
* Copyright (C) 2011-2020 Andrew Pavlin, KA2DDO
* This file is part of YAAC (Yet Another APRS Client).
*
* YAAC is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* YAAC 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
* and GNU Lesser General Public License along with YAAC. If not,
* see <http://www.gnu.org/licenses/>.
*/
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;
import org.ka2ddo.aprs.PositionlessWeatherReport;
import org.ka2ddo.ax25.AX25Callsign;
import org.ka2ddo.ax25.AX25Frame;
import org.ka2ddo.ax25.TransmittingConnector;
import org.ka2ddo.gps.GpsFix;
import org.ka2ddo.gps.GpsFixQuality;
import org.ka2ddo.util.FastBlockingQueue;
import org.ka2ddo.util.ReschedulableTimerTask;
import org.ka2ddo.yaac.YAAC;
import org.ka2ddo.yaac.core.HelpTaggedIOException;
import org.ka2ddo.yaac.gps.GPSDistributor;
import org.ka2ddo.yaac.io.KissEscapeOutputStream;
import org.ka2ddo.yaac.io.NonshareableBufferedDataOutputStream;
import org.ka2ddo.yaac.io.PortConfig;
import org.ka2ddo.yaac.io.PortManager;
import org.ka2ddo.yaac.io.TNCConnector;
import org.ka2ddo.yaac.io.Transmitter;
import org.ka2ddo.yaac.weather.WeatherDistributor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
/**
* This class implements a port to a TNC connected by a serial port using KISS
* over asynchronous serial data via RS-232C or USB. Note this can only handle one TNC
* per serial port; it does not support the multiplexing with port numbers in the
* high nibble of the KISS command byte, but only expects port#0, although it does
* support other device numbers in the KISS packets to support the Byonics TinyTrak 4's
* GPS feed-through multiplexing (which ensures the GPS data doesn't get mixed with
* the AX.25 packet data).
* @author Andrew Pavlin, KA2DDO
*/
public class SerialTNCConnector extends TNCConnector implements SerialPortEventListener, TransmittingConnector {
/**
* The PortConnector type name for SerialTNCConnector.
*/
public static final String TYPE_NAME = "Serial_TNC";
/**
* The fully-qualified platform-wildcarded class name for the configuration editor for SerialTNCConnector.
*/
public static final String CONFIG_GUI = "org.ka2ddo.yaac.legacyrxtx.*.SerialTNCPortEditor";
private static final EnumSet<PortConfig.Fields> x_BLANK_FIELDS = EnumSet.noneOf(PortConfig.Fields.class);
/**
* Set of PortConfig field identifiers for fields that should not be copied from another system and should therefore be
* blanked out when copying configuration files.
*/
public static final Set<PortConfig.Fields> BLANK_FIELDS = Collections.unmodifiableSet(x_BLANK_FIELDS);
private static final IdentityHashMap<PortConfig.Fields, PortConfig.RequireHints> x_REQ_FIELDS = new IdentityHashMap<PortConfig.Fields, PortConfig.RequireHints>();
/**
* List of PortConfig field identifiers that are mandated for this port type to work, and the ResourceBundle key to
* prompt for a value if missing.
*/
public static final Map<PortConfig.Fields, PortConfig.RequireHints> REQ_FIELDS = Collections.unmodifiableMap(x_REQ_FIELDS);
static {
x_BLANK_FIELDS.add(PortConfig.Fields.deviceName);
x_REQ_FIELDS.put(PortConfig.Fields.deviceName, new PortConfig.RequireHints("ConfigImporter.serialPort", PortConfig.HintType.SERIAL_PORTS));
x_BLANK_FIELDS.add(PortConfig.Fields.callsign);
x_REQ_FIELDS.put(PortConfig.Fields.callsign, new PortConfig.RequireHints("ConfigImporter.callsign", PortConfig.HintType.CALLSIGN_SSID));
}
private CommPortIdentifier serialPortId;
private SerialPort serialPort;
private transient String cachedToString = null;
private NonshareableBufferedDataOutputStream os = null;
private KissEscapeOutputStream kos = null;
private InputStream is;
private byte[] rcvBuf = new byte[2048];
private int wEnd = 0;
private int cmdPromptCheck = 0;
private int kissForceCRCount = 0;
private transient long frameStartTime = -1L;
private transient int fixedPriority = Thread.MIN_PRIORITY - 1;
private transient int numBytesPerEvent = 1;
private transient ReschedulableTimerTask timeslotTask = null;
private transient FastBlockingQueue<AX25Frame> timeslotQueue = null;
private transient long nextAllowableTransmitTime = -1L;
private transient boolean rcvdGPSOverKISS = false;
private transient boolean rcvdWxOverKISS = false;
private static final char[] CMD_MODE_TNC_PROMPT = { 'c', 'm', 'd', ':' };
private static final HashMap<Character,Character> ESCAPED_CHAR = new HashMap<Character, Character>();
static {
ESCAPED_CHAR.put('r', '\r');
ESCAPED_CHAR.put('n', '\n');
ESCAPED_CHAR.put('t', '\t');
ESCAPED_CHAR.put('0', '\0');
ESCAPED_CHAR.put('\\', '\\');
ESCAPED_CHAR.put('f', '\f');
ESCAPED_CHAR.put('b', '\b');
ESCAPED_CHAR.put('\'', '\'');
ESCAPED_CHAR.put('e', '\033');
ESCAPED_CHAR.put('"', '"');
ESCAPED_CHAR.put('w', null);
}
private transient KissEscapeOutputStream.RcvState curState = KissEscapeOutputStream.RcvState.IDLE;
/**
* Create an uninitialized SerialTNCConnector instance, to be configured with a later call to configure().
* @see #configure(PortConfig)
*/
public SerialTNCConnector() {}
/**
* Set the correct defaults for a port in process of being created.
* @param cfg PortConfig.Cfg whose defaults should be adjusted
*/
public static void fillinConfigDefaults(PortConfig.Cfg cfg) {
if (cfg.deviceName.length() == 0) {
// new config, fill with defaults
cfg.digiAliases = new String[]{"TEMP1"};
cfg.baudRate = 9600;
}
}
/**
* Update the configuration of the connector to match the updated
* setup.
* @param portConfig PortConfig defining new port settings
* @throws IOException if interface changes could not be applied
* @throws IllegalArgumentException if type information is invalid for
* changing the settings of this PortConnector
*/
public void configure(PortConfig portConfig) throws IOException, IllegalArgumentException {
if (!TYPE_NAME.equals(portConfig.portType)) {
throw new IllegalArgumentException("invalid portType=" + portConfig.portType);
}
setPortConfig(portConfig);
if (serialPortId != null) {
if (!portConfig.enabled || !serialPortId.getName().equals(currentCfg.deviceName)) {
this.close();
}
}
try {
if (portConfig.enabled) {
if (serialPort == null) {
serialPortId = CommPortIdentifier.getPortIdentifier(currentCfg.deviceName);
serialPort = (SerialPort)serialPortId.open("YAAC(SerialTNC," + currentCfg.deviceName + ')', 1000);
System.out.println(new Date().toString() + ": opened SerialTNC " + currentCfg.deviceName + " default input buf size=" + serialPort.getInputBufferSize());
is = serialPort.getInputStream();
os = new NonshareableBufferedDataOutputStream(serialPort.getOutputStream(), 4096);
kos = new KissEscapeOutputStream((OutputStream)os, (currentCfg.flags & PortConfig.FLAGS_USE_WORKAROUND) != 0);
curState = KissEscapeOutputStream.RcvState.IDLE;
serialPort.addEventListener(this);
serialPort.notifyOnDataAvailable(true);
serialPort.notifyOnOverrunError(true);
}
if (currentCfg.baudRate != 0) {
serialPort.setSerialPortParams(currentCfg.baudRate,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
}
if (currentCfg.flowControlled) {
if (!serialPort.isDSR() && (currentCfg.flags & PortConfig.FLAGS_USE_WORKAROUND) == 0) {
// only do this if not a Kenwood radio, which isn't wired to provide DSR
throw new HelpTaggedIOException(MessageFormat.format(YAAC.getMsg("dialog.IOError.checkModem"), TYPE_NAME, currentCfg.deviceName), "FAQ.oldTNC");
}
boolean modemOn = (currentCfg.flags & (PortConfig.FLAGS_LOCAL|PortConfig.FLAGS_NODTR)) == 0; // LOCAL is for backward compatibility
serialPort.setDTR(modemOn);
serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_IN | SerialPort.FLOWCONTROL_RTSCTS_OUT);
} else {
serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
boolean modemOn = (currentCfg.flags & (PortConfig.FLAGS_LOCAL|PortConfig.FLAGS_NODTR)) == 0; // LOCAL is for backward compatibility
serialPort.setDTR(modemOn);
serialPort.setRTS(modemOn);
}
if (currentCfg.callsign.length() > 0) {
setPortAddress(new AX25Callsign(currentCfg.callsign));
} else {
throw new HelpTaggedIOException(MessageFormat.format(YAAC.getMsg("dialog.IOError.cantOpenNoCallsign"), TYPE_NAME, currentCfg.deviceName), "FAQ.oldTNC");
}
forceToKissMode();
cachedToString = null;
rcvdGPSOverKISS = false;
rcvdWxOverKISS = false;
PortManager.firePortOpened(this);
// set up transmission queue
timeslotQueue = new FastBlockingQueue<>(16);
// set up queue-draining timer
timeslotTask = new ReschedulableTimerTask() {
public void run() {
long endOfSlot = Long.MAX_VALUE;
if (currentCfg.timeslotLength > 0) {
endOfSlot = System.currentTimeMillis() + currentCfg.timeslotLength * 1000L;
}
while (true) {
AX25Frame drain = null;
synchronized (SerialTNCConnector.this) {
if (timeslotQueue != null && timeslotQueue.size() > 0) {
drain = timeslotQueue.poll();
}
}
if (drain != null) {
try {
synchronized (SerialTNCConnector.this) {
if (os == null) {
// abort if closed in middle of write loop
return;
}
sendFrameNow(drain);
if (endOfSlot < nextAllowableTransmitTime) {
break; // can't send any more until next assigned timeslot
}
}
} catch (IOException e) {
e.printStackTrace(System.out);
}
} else {
break;
}
}
}
};
if (currentCfg.timeslotCycleLength > 0) {
scheduleTimeslotTimer();
}
}
} catch (Exception e) {
System.out.println(new Date().toString() + ": failed to open SerialTNC " + currentCfg.deviceName + ": ");
e.printStackTrace(System.out);
throw new HelpTaggedIOException(MessageFormat.format(YAAC.getMsg("dialog.IOError.unableToOpen"), TYPE_NAME, currentCfg.deviceName), e, "FAQ.oldTNC");
}
}
private void scheduleTimeslotTimer() {
GpsFix fix = GPSDistributor.getInstance().getCurrentFix();
long skewCorr = 0;
if (fix != null && (fix.quality == GpsFixQuality.DGPS || fix.quality == GpsFixQuality.GPS_SPS || fix.quality == GpsFixQuality.PPS)) {
skewCorr = -fix.localToGpsClockDiff;
}
long wakeup;
if (currentCfg.timeslotCycleLength > 0) {
long now = System.currentTimeMillis();
// figure the next cycle startup time
long timeslotCycleLengthMsec = currentCfg.timeslotCycleLength * 1000L;
wakeup = (now + skewCorr + timeslotCycleLengthMsec) / timeslotCycleLengthMsec;
wakeup *= timeslotCycleLengthMsec;
// add our offset
wakeup += currentCfg.timeslotOffset * 1000L;
// back up one cycle if we have time
if (wakeup - now > timeslotCycleLengthMsec + 1000L) {
wakeup -= timeslotCycleLengthMsec;
}
timeslotTask.resched(timeslotTimer, new Date(wakeup), currentCfg.timeslotCycleLength);
} else {
wakeup = nextAllowableTransmitTime;
timeslotTask.resched(timeslotTimer, new Date(wakeup));
}
}
private void forceToKissMode() throws IOException {
curState = KissEscapeOutputStream.RcvState.IDLE;
wEnd = 0; // discard everything in the receive buffer
if (currentCfg.filter.length() > 0) {
synchronized (this) {
kissForceCRCount = 1; // account for one extra CR, to prevent thrashing
for (int i = 0; i < currentCfg.filter.length(); i++) {
char ch = currentCfg.filter.charAt(i);
if ('\\' == ch && i < currentCfg.filter.length() - 1) {
Character c = ESCAPED_CHAR.get(currentCfg.filter.charAt(++i));
if (c != null) {
os.write(c);
os.flush();
if ('\r' == c) {
kissForceCRCount += 2; // assume it will be echoed back plus a response line
try {
wait(300L);
} catch (InterruptedException e) {
// do nothing, we're just stalling to let the TNC operate
}
} else {
try {
wait(100L);
} catch (InterruptedException e) {
// do nothing, we're just stalling to let the TNC operate
}
}
} else {
try {
wait(1000L);
} catch (InterruptedException e) {
// do nothing, we're just stalling to let the TNC operate
}
}
} else {
os.write(ch);
}
}
os.flush();
}
}
}
/**
* Reports whether this PortConnector has an open connection to its port.
*
* @return boolean true if PortConnector is open
*/
public boolean isOpen() {
return serialPort != null;
}
/**
* Transmit an AX.25 frame through this port. May delay packet if using timeslotting or
* still transmitting another packet.
*
* @param frame AX25Frame object to transmit
* @throws IOException if transmit failed for any reason other than a receive-only port
*/
public synchronized void sendFrame(AX25Frame frame) throws IOException {
if (os != null && currentCfg.transmitAllowed) {
if (currentCfg.timeslotCycleLength <= 0 && System.currentTimeMillis() >= nextAllowableTransmitTime) {
sendFrameNow(frame);
} else {
// queue outbound traffic for timeslot transmit task (coalescing duplicates such as the beacon)
if (!timeslotQueue.contains(frame)) {
timeslotQueue.add(frame);
scheduleTimeslotTimer();
}
}
}
}
/**
* Transmit one frame through this port. Must be called from a method synchronized on the SerialTNCConnector
* object, and caller is responsible for determining whether this port is enabled for transmissions.
* @param frame AX25Frame to send; if sender is missing, the port's callsign is inserted
* @throws IOException if transmit fails for any reason
*/
private void sendFrameNow(AX25Frame frame) throws IOException {
fireTransmitting(true);
long startTime = System.currentTimeMillis();
if (frame.sender == null || (frame.sourcePort != null && frame.sourcePort.hasCapability(CAP_IGATE))) {
frame.sender = new AX25Callsign(currentCfg.callsign);
frame.sender.h_c = !frame.dest.h_c;
}
kos.resetByteCount();
os.write(KissEscapeOutputStream.FEND);
kos.write(0); // data frame to TNC port 0
frame.write(kos);
os.write(KissEscapeOutputStream.FEND);
os.flush();
int byteCount = kos.getByteCount();
stats.numXmtBytes += byteCount;
stats.numXmtFrames++;
long now = System.currentTimeMillis();
Transmitter.getInstance().logTransmittedPacket(frame, now);
int estElapsedTimeUntilPacketisTransmitted = (int) ((getRFSendTimePerByte() * (byteCount + 10)) +
// next component is guesstimate of how long until packet is actually started transmitting
// based on serial port to TNC transmission time
((byteCount * 10.0F / currentCfg.baudRate) - (now - startTime)));
nextAllowableTransmitTime = now + estElapsedTimeUntilPacketisTransmitted;
fireTransmitting(false);
}
/**
* Process incoming serial port event.
* @param event SerialPortEvent describing the handling needed
*/
public void serialEvent(SerialPortEvent event) {
// make this more real-time
Thread thread = Thread.currentThread();
if (thread.getPriority() <= Thread.NORM_PRIORITY) {
if (fixedPriority < Thread.MIN_PRIORITY) {
thread.setPriority(Thread.NORM_PRIORITY + 1);
fixedPriority = thread.getPriority();
}
}
if (is == null) {
return; // avoid race conditions on port close
}
switch (event.getEventType()) {
case SerialPortEvent.DATA_AVAILABLE:
int newData;
long lastRcvByteCount = stats.numRcvBytes;
try {
int availBytes;
while (is != null && (availBytes = is.available()) > 0) {
for (int count = 0; count < availBytes; count++) {
if (is == null) {
break;
}
newData = is.read();
switch (curState) {
case IDLE:
if (KissEscapeOutputStream.FEND == newData) {
curState = KissEscapeOutputStream.RcvState.IN_FRAME;
wEnd = 0;
kissForceCRCount = 0;
} else if ('\r' == newData) {
if (currentCfg.filter.length() > 0) { // has a command for switching to KISS mode?
if (kissForceCRCount <= 0) {
// try forcing TNC into KISS mode again
System.out.println(new Date().toString() + ": trying to force echoing TNC back into KISS mode again");
kissForceCRCount = 0; // in case it got screwed up somehow
forceToKissMode();
} else {
kissForceCRCount--;
}
}
}
break;
case IN_FRAME:
switch (newData) {
case KissEscapeOutputStream.FEND:
// send the just-finished frame up to the next layer
if (wEnd > 1) {
if (CMD_MODE_TNC_PROMPT.length == cmdPromptCheck) {
// try forcing TNC into KISS mode again
System.out.println(new Date().toString() + ": " + currentCfg.deviceName + ": bogusframe, trying to force TNC back into KISS mode again");
curState = KissEscapeOutputStream.RcvState.IDLE;
forceToKissMode();
cmdPromptCheck = 0;
break; // don't waste any more time on processing a "frame" we're discarding
// not just a stream of frame borders....
} else if ((rcvBuf[0] & 0x0F) == 0) { // KISS data frame?
long now = System.currentTimeMillis();
long transTime = now - frameStartTime;
if (transTime > wEnd * 5L) {
System.out.println(new Date(now).toString() + ":" + currentCfg.deviceName + ": excessively long time " + transTime + "msec to receive " + wEnd + "-byte frame");
}
AX25Frame frame = null;
try {
if (rcvBuf[1] == '$' && (rcvBuf[0] & 0xF0) != 0) {
// KISS deviceID >= 1 and it looks like GPS sentence (AX.25 frame can't start with 0x24 as first byte of dest addr)
String line = new String(rcvBuf, 1, wEnd);
int delim = line.indexOf('\r');
if (delim > 0) {
line = line.substring(0, delim);
}
if (line.startsWith("$PKWDWXI")) {
if (!rcvdWxOverKISS) {
rcvdWxOverKISS = true;
PortManager.firePortStatusChanged(this);
}
PositionlessWeatherReport pwr = new PositionlessWeatherReport(line, 8);
Map<Enum, Object> readOnlyExtensionMap = pwr.getReadOnlyExtensionMap();
if (readOnlyExtensionMap.size() > 0) {
WeatherDistributor.getInstance().loadCurrentWeather(readOnlyExtensionMap);
}
} else {
if (!rcvdGPSOverKISS) {
rcvdGPSOverKISS = true;
PortManager.firePortStatusChanged(this);
}
// GPS forwarded by Byonics TinyTrak4???
GPSDistributor.getInstance().parseNMEA0183DataLine(line, this, true, currentCfg.callsign);
}
} else {
frame = fireConsumeFrame(rcvBuf, 1, wEnd - 1, frameStartTime);
}
} catch (Exception e) {
System.out.println(new Date().toString() + ": SerialTNCConnector unable to handle " + (wEnd - 1) + "-byte frame:");
StringBuilder b = new StringBuilder(wEnd * 2);
for (int i = 1; i < wEnd; i++) {
byte ch = rcvBuf[i];
b.append(' ').append(Integer.toHexString(ch & 0xFF));
}
System.out.println(b.toString());
e.printStackTrace(System.out);
stats.numBadRcvFrames++;
}
long afterConsume = System.currentTimeMillis();
long consumeTime = afterConsume - now;
if (consumeTime > 50) {
System.out.println(new Date(afterConsume).toString() + ":" + currentCfg.deviceName + ": excessively long time " + consumeTime + "msec to queue " + wEnd + "-byte frame " + frame);
}
} else {
System.out.println(new Date().toString() + ": Serial_TNC[" + currentCfg.deviceName + "]: discarding type=" + Integer.toHexString((int) rcvBuf[0] & 0xFF) + " KISS frame len=" + (wEnd - 1));
}
stats.numRcvFrames++;
stats.numRcvBytes += wEnd - 1;
}
// reset the receive buffer for the next frame
wEnd = 0;
frameStartTime = -1L;
fireReceiving(false);
break;
case KissEscapeOutputStream.FESC:
if (-1L == frameStartTime) {
frameStartTime = System.currentTimeMillis() - 20000 / currentCfg.baudRate - 1; // account for transmission time of first byte and frame byte
fireReceiving(true);
cmdPromptCheck = 0; // obviously can't be in TNC prompt sequence
}
curState = KissEscapeOutputStream.RcvState.IN_ESC;
break;
default:
long now = System.currentTimeMillis();
if (-1L == frameStartTime) {
frameStartTime = now - 20000 / currentCfg.baudRate - 1; // account for transmission time of first byte and frame byte
fireReceiving(true);
} else if (now - frameStartTime > 5000L) {
if (cmdPromptCheck == CMD_MODE_TNC_PROMPT.length) {
// try forcing TNC into KISS mode again
System.out.println(new Date().toString() + ": " + currentCfg.deviceName + ": trying to force TNC back into KISS mode again");
forceToKissMode();
cmdPromptCheck = 0;
break; // don't waste any more time on processing a "frame" we're discarding
}
}
if (cmdPromptCheck < CMD_MODE_TNC_PROMPT.length && newData == CMD_MODE_TNC_PROMPT[cmdPromptCheck]) {
cmdPromptCheck++;
} else {
cmdPromptCheck = 0;
}
if (wEnd == rcvBuf.length / 2) {
System.out.println(new Date().toString() + ": " + currentCfg.deviceName + ": excessively long packet, must be mode garbling, force into KISS again");
forceToKissMode();
}
if (wEnd < rcvBuf.length) {
rcvBuf[wEnd++] = (byte) newData;
} else {
// some kind of protocol error, so reset and start over
System.out.println(new Date().toString() + ": " + currentCfg.deviceName + ": receive buffer overflow, must be mode garbling, reset protocol");
wEnd = 0;
fireReceiving(false);
curState = KissEscapeOutputStream.RcvState.IDLE;
}
break;
}
break;
case IN_ESC:
stats.numRcvBytes++;
switch (newData) {
case KissEscapeOutputStream.TFEND:
rcvBuf[wEnd++] = (byte) KissEscapeOutputStream.FEND;
break;
case KissEscapeOutputStream.TFESC:
rcvBuf[wEnd++] = (byte) KissEscapeOutputStream.FESC;
break;
default:
rcvBuf[wEnd++] = (byte) newData;
break;
}
curState = KissEscapeOutputStream.RcvState.IN_FRAME;
break;
}
}
}
} catch (IOException e) {
stats.numBadRcvFrames++;
System.out.println(new Date().toString() + ": IOException in SerialTNCConnector[" + currentCfg.deviceName + "], closing port");
e.printStackTrace(System.out);
// discard this frame
curState = KissEscapeOutputStream.RcvState.IDLE;
fireFailed();
close();
} catch (Throwable e) {
stats.numBadRcvFrames++;
System.out.println(new Date().toString() + ": unhandled exception in SerialTNCConnector[" + currentCfg.deviceName + ']');
e.printStackTrace(System.out);
// discard this frame
curState = KissEscapeOutputStream.RcvState.IDLE;
}
int numNewBytes = (int)(stats.numRcvBytes - lastRcvByteCount);
if (numNewBytes > numBytesPerEvent) {
numBytesPerEvent = numNewBytes;
System.out.println(new Date().toString() + ": " + currentCfg.deviceName + ": got as many as " + numBytesPerEvent + " bytes in one chunk");
}
break;
case SerialPortEvent.OE:
System.out.println(new Date().toString() + ": Serial_TNC: receive data overrun on " + portConfig.portType + ' ' + currentCfg.deviceName);
stats.numDataOverrunLosses++;
stats.numRcvBytes++; // at least one lost
break;
case SerialPortEvent.CTS:
if (event.getOldValue() && !event.getNewValue()) {
try {
serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_OUT|SerialPort.FLOWCONTROL_RTSCTS_IN);
System.out.println(new Date().toString() + ": " + currentCfg.deviceName + ": switching to hardware flow control...");
} catch (UnsupportedCommOperationException e) {
e.printStackTrace();
}
}
break;
case SerialPortEvent.DSR:
if (event.getOldValue() && !event.getNewValue()) {
System.out.println(new Date().toString() + ": " + currentCfg.deviceName + ": lost DSR");
}
break;
default:
System.out.println("other SerialPortEvent=" + event.getEventType());
break;
}
}
/**
* Shut down this port connection.
*/
@Override
public void close() {
if (serialPort != null) {
if (timeslotTask != null) {
timeslotTask.cancel();
timeslotTask = null;
}
if (timeslotQueue != null) {
// any pending messages just get lost, sorry.
timeslotQueue = null;
}
if (os != null) {
try {
if (currentCfg.filter.length() > 0) {
// we switched into KISS mode, so get us out
os.write(KissEscapeOutputStream.FEND);
os.write(0xFF);
os.write(KissEscapeOutputStream.FEND);
os.flush();
}
os.close();
os = null;
kos = null;
} catch (IOException e) {
System.out.print("error switching TNC out of KISS mode: ");
e.printStackTrace(System.out);
}
}
if (is != null) {
try {
is.close();
is = null;
} catch (IOException e) {
System.out.print("error closing TNC input stream: ");
e.printStackTrace(System.out);
}
}
serialPort.close();
serialPort = null;
serialPortId = null;
cachedToString = null;
fireReceiving(false);
fireTransmitting(false);
PortManager.firePortClosed(this);
}
}
/**
* Returns a string representation of the object.
*
* @return a string representation of the object.
*/
@Override
public String toString() {
if (cachedToString == null) {
cachedToString = TYPE_NAME + ": " + (serialPortId == null ? "???" : serialPortId.getName()) + '[' + getCallsign() + ']';
}
return cachedToString;
}
/**
* Specify what capabilities a port of this type has.
* @return bitmask of capability flags
*/
@Override
public int getCapabilities() {
int cap = CAP_RCV_PACKET_DATA|CAP_RF;
PortConfig.Cfg currentCfg;
if ((currentCfg = this.currentCfg) != null) {
if (currentCfg.transmitAllowed) {
cap |= CAP_XMT_PACKET_DATA;
}
if ((currentCfg.acceptableProtocolsMask & PortConfig.PROTOCOL_OPENTRAC) != 0) {
cap |= CAP_OPENTRAC;
}
if ((currentCfg.acceptableProtocolsMask & PortConfig.PROTOCOL_AX25) != 0) {
cap |= CAP_RAW_AX25;
}
if ((currentCfg.flags & PortConfig.FLAGS_HF) != 0) {
cap |= CAP_HF;
}
}
if (rcvdGPSOverKISS) {
cap |= CAP_GPS_DATA;
}
if (rcvdWxOverKISS) {
cap |= CAP_WEATHER;
}
return cap;
}
}

Some files were not shown because too many files have changed in this diff Show More