Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FGCom-mumble support (implements #5) #2

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/de/knewcleus/openradar/gui/setup/AirportData.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public class AirportData implements INavPointListener {
private HashSet<String> activeStartingRouteRunways = new HashSet<>();

public enum FgComMode {
Auto, Internal, External, Off
Auto, Internal, External, Mumble, Off
};

private FgComMode fgComMode = FgComMode.Internal;
Expand Down
15 changes: 13 additions & 2 deletions src/de/knewcleus/openradar/gui/setup/SetupDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public class SetupDialog extends JFrame {

private FgComMode fgComMode = FgComMode.Internal;
private String[] modeModel = new String[] { "Auto: Use the internal fgcom", "Internal: OR starts and controls a fgcom client",
"External: Control external fgcom client instance", "OFF: No FgCom support" };
"External: Control external fgcom client instance", "Mumble: Connect to FGCom-mumble plugin", "OFF: No FgCom support" };

private List<Image> icons = new ArrayList<Image>();

Expand Down Expand Up @@ -1672,8 +1672,10 @@ private void loadProperties() {
cbFgComMode.setSelectedIndex(1);
if (fgComMode == FgComMode.External)
cbFgComMode.setSelectedIndex(2);
if (fgComMode == FgComMode.Off)
if (fgComMode == FgComMode.Mumble)
cbFgComMode.setSelectedIndex(3);
if (fgComMode == FgComMode.Off)
cbFgComMode.setSelectedIndex(4);

tfFgComPath.setText(p.getProperty("fgCom.path", ""));
tfFgComPath.setText(p.getProperty("fgCom.path", ""));
Expand Down Expand Up @@ -1893,6 +1895,15 @@ public void actionPerformed(ActionEvent e) {
tfFgComHost.setEnabled(true);
tfFgComServer.setEnabled(false);
} else if (cbFgComMode.getSelectedIndex() == 3) {
fgComMode = FgComMode.Mumble;
cbFgComMode.setToolTipText("You will start Mumble with FGCom-mumble plugin yourself and OpenRadar will control it!");
// FGCom-mumble
tfFgComPath.setEnabled(false);
tfFgComExec.setEnabled(false);
tfFgComPorts.setEnabled(true);
tfFgComHost.setEnabled(true);
tfFgComServer.setEnabled(false);
} else if (cbFgComMode.getSelectedIndex() == 4) {
fgComMode = FgComMode.Off;
cbFgComMode.setToolTipText("FGCom will not be controlled by OpenRadar");
// off
Expand Down
34 changes: 17 additions & 17 deletions src/de/knewcleus/openradar/gui/status/radio/FgComController.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,22 @@
*/
public class FgComController implements Runnable, IRadioBackend {

private Thread thread = new Thread(this, "OpenRadar - FGComController");
private GuiMasterController master = null;
private volatile double lon;
private volatile double lat;
private volatile double alt;
private Map<String, Process> fgComProcesses = Collections.synchronizedMap(new TreeMap<String, Process>());
private List<LogWriterThread> logWriters = Collections.synchronizedList(new ArrayList<LogWriterThread>());
protected Thread thread = new Thread(this, "OpenRadar - FGComController");
protected GuiMasterController master = null;
protected volatile double lon;
protected volatile double lat;
protected volatile double alt;
protected Map<String, Process> fgComProcesses = Collections.synchronizedMap(new TreeMap<String, Process>());
protected List<LogWriterThread> logWriters = Collections.synchronizedList(new ArrayList<LogWriterThread>());

private final Map<String, Radio> radios = Collections.synchronizedMap(new TreeMap<String, Radio>());
protected final Map<String, Radio> radios = Collections.synchronizedMap(new TreeMap<String, Radio>());

private DatagramSocket datagramSocket;
protected DatagramSocket datagramSocket;

private volatile boolean isRunning = true;
private int sleeptime = 500;
protected volatile boolean isRunning = true;
protected int sleeptime = 500;

private final static Logger log = LogManager.getLogger(FgComController.class);
protected final static Logger log = LogManager.getLogger(FgComController.class);

public FgComController() {
}
Expand Down Expand Up @@ -119,7 +119,7 @@ public void run() {
Runtime.getRuntime().addShutdownHook(closeChildThread);
}

private void endFgComProcesses() {
protected void endFgComProcesses() {

for(LogWriterThread lw : logWriters) {
lw.stop(); // marks them to exit
Expand Down Expand Up @@ -187,7 +187,7 @@ public void run() {
}
}

private void sendSettings(Radio r) {
protected void sendSettings(Radio r) {
if(master.getCurrentATCCallSign()!=null && !master.getCurrentATCCallSign().isEmpty()) { // after initialization
try {
String message = composeMessage(r);
Expand All @@ -202,7 +202,7 @@ private void sendSettings(Radio r) {
}
}

private String composeMessage(Radio r) {
protected String composeMessage(Radio r) {
// COM1_FRQ=120.500,COM1_SRV=1,COM2_FRQ=118.300,COM2_SRV=1,NAV1_FRQ=115.800,NAV1_SRV=1,NAV2_FRQ=116.800,NAV2_SRV=1,PTT=0,TRANSPONDER=0,IAS=09.8,GS=00.0,LON=-122.357193,LAT=37.613548,ALT=00004,HEAD=269.9,CALLSIGN=D-W794,MODEL=Aircraft/c172p/Models/c172p.xml
StringBuilder sb = new StringBuilder();
sb.append("PTT=");
Expand Down Expand Up @@ -371,7 +371,7 @@ public void initFgCom(Radio r, String pathToFgComExec, String fgComExec, String
}
}

private String buildString(List<String> list) {
protected String buildString(List<String> list) {
StringBuilder sb = new StringBuilder();
for(String s : list) {
if(sb.length()>0) {
Expand Down Expand Up @@ -432,7 +432,7 @@ public static String getFgComSpecialsPath(AirportData data, String pathToFgComEx
return specialsPath;
}

private static String getFgComBasePath(String pathToFgComExec) {
protected static String getFgComBasePath(String pathToFgComExec) {
String path = pathToFgComExec.trim();
if(path.contains(File.separator)) {
File parentDir = pathToFgComExec.isEmpty() ? new File(System.getProperty("user.dir")) : new File(pathToFgComExec).getParentFile();
Expand Down
212 changes: 212 additions & 0 deletions src/de/knewcleus/openradar/gui/status/radio/FgComMumbleController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/**
* Copyright (C) 2020 Benedikt Hallinger
*
* This file is part of OpenRadar.
*
* OpenRadar is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* OpenRadar is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* OpenRadar. If not, see <http://www.gnu.org/licenses/>.
*
* Diese Datei ist Teil von OpenRadar.
*
* OpenRadar ist Freie Software: Sie können es unter den Bedingungen der GNU
* General Public License, wie von der Free Software Foundation, Version 3 der
* Lizenz oder (nach Ihrer Option) jeder späteren veröffentlichten Version,
* weiterverbreiten und/oder modifizieren.
*
* OpenRadar wird in der Hoffnung, dass es nützlich sein wird, aber OHNE JEDE
* GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite Gewährleistung der
* MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. Siehe die GNU General
* Public License für weitere Details.
*
* Sie sollten eine Kopie der GNU General Public License zusammen mit diesem
* Programm erhalten haben. Wenn nicht, siehe <http://www.gnu.org/licenses/>.
*/
package de.knewcleus.openradar.gui.status.radio;

import java.io.File;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import de.knewcleus.openradar.gui.GuiMasterController;
import de.knewcleus.openradar.gui.setup.AirportData;
import de.knewcleus.openradar.gui.setup.AirportData.FgComMode;
import java.security.InvalidParameterException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* This class controls the FgCom-mumble plugin.
*
* Message format is described in detail at https://github.com/hbeni/fgcom-mumble/blob/master/client/plugin.spec.md
*
* @author Benedikt Hallinger
*
*/
public class FgComMumbleController extends FgComController implements Runnable, IRadioBackend {

protected final static Logger log = LogManager.getLogger(FgComMumbleController.class);

private boolean comZeroDetected = false;

public FgComMumbleController() {
}

public FgComMumbleController(GuiMasterController master, String aircraftModel, double lon, double lat, double alt) {
super(master, aircraftModel, lon, lat, alt);
}


@Override
public void run() {
while (isRunning) {
synchronized (this) {
sendSettings();
}
try {
Thread.sleep(sleeptime);
} catch (InterruptedException e) {
}
}
}

@Override
protected synchronized void sendSettings(Radio r) {
this.sendSettings();
}

/*
* Compose and send FGCom-mumble protocol messages
*/
protected synchronized void sendSettings() {
if (master.getCurrentATCCallSign()!=null && !master.getCurrentATCCallSign().isEmpty()) { // after initialization

String message = "";

message += "CALLSIGN=";
message += master.getCurrentATCCallSign();
message += String.format(",LAT=%.6f",lat);
message += String.format(",LON=%.6f",lon); //.replaceAll(",", ".")
message += String.format(",ALT=%1.0f",alt);

// Compose individual radio state
for (Radio r : radios.values()) {
if (r.getCallSign() != null && r.getFgComPort() > 0) {
message += composeMessage(r);
}
}

// Terminate the message
message += System.lineSeparator();

// Finally try to send
try {
// System.out.println("Sending fgcom message: "+message);
byte[] msgBytes = message.getBytes(Charset.forName("ISO-8859-1"));
DatagramPacket packet = new DatagramPacket(msgBytes, msgBytes.length);

// send all info to all configured radio ports, but only once
Vector<Integer> seenPorts = new Vector();
for (Radio r : radios.values()) {
int port = r.getFgComPort();

if (!seenPorts.contains(port)) {
packet.setSocketAddress(new InetSocketAddress(r.getFgComHost(), port));
datagramSocket.send(packet);
seenPorts.add(port);
log.debug("UDP-MSG sent: "+message);
}
}
} catch (IOException e) {
log.error("Error while tuning FGCOM!",e);
}
}
}

/*
* Compose UDP message string for the given radio
*/
protected String composeMessage(Radio r) {
StringBuilder sb = new StringBuilder();
Touple<String, Integer> rName = this.splitComName(r.getKey());
if (rName.left != null && rName.right != null) {
String rId = rName.left;
Integer rNr = rName.right;
if (rNr == 0 ) comZeroDetected = true; // COM0 detected
if (comZeroDetected) rNr++; // If COM0 was detected: add 1, because COMs start with COM1!
String rKey = rId + rNr.toString();

sb.append(","+rKey+"_PTT=");
sb.append(r.isPttActive() ? "1" : "0");
sb.append(","+rKey+"_FRQ=");
sb.append(r.getFrequency());
sb.append(","+rKey+"_VOL=");
sb.append(String.format("%.1f",r.getVolumeF()));//.replaceAll(",", ".")

//System.out.println(sb.toString());
return sb.toString();
} else {
throw new InvalidParameterException("Could not parse Touple from com="+r.getKey());
}
}

/*
* Split COMn name into Radio type and Number
*/
protected Touple<String, Integer> splitComName(String com) {
Pattern p = Pattern.compile("(\\w+)(\\d+)");
Matcher m = p.matcher(com);
if (m.matches()) {
Touple<String, Integer> t = new Touple<>(m.group(1), Integer.valueOf(m.group(2)));
return t;
} else {
throw new InvalidParameterException("Radio key '"+com+"' not in valid syntax COMn!");
}

}

@Override
public synchronized void addRadio(String pathToFgComExec, String fgComExec, String key, String fgComServer, String fgComHost, int localFgComPort,
String callSign, RadioFrequency frequency) {
String key_derived = "COM"+radios.size();
Radio r = new Radio(key_derived, fgComHost, localFgComPort, callSign, frequency);
//initFgCom(r, pathToFgComExec, fgComExec, fgComServer, localFgComPort);
radios.put(key_derived, r);
}


/*
* Simple Touple class
*/
protected class Touple<Ta,Tb> {
Ta left;
Tb right;
Touple(Ta a, Tb b) {
left = a;
right = b;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ public RadioController(GuiMasterController guiInteractionManager) {
public void init() {
AirportData data = master.getAirportData();
if(data.getFgComMode()!=FgComMode.Off) {
fgComController = new FgComController(master, data.getModel(), data.getLon(), data.getLat(), data.getElevationFt());
if (data.getFgComMode() == FgComMode.Mumble) {
fgComController = new FgComMumbleController(master, data.getModel(), data.getLon(), data.getLat(), data.getElevationFt());
} else {
fgComController = new FgComController(master, data.getModel(), data.getLon(), data.getLat(), data.getElevationFt());
}

int i = 0;
for (Radio r : data.getRadios().values()) {
Expand Down