From e71a53c5be39f4d780a19ac5e19799e0c1368b6f Mon Sep 17 00:00:00 2001 From: sh123 Date: Sat, 20 Aug 2022 15:52:10 +0300 Subject: [PATCH] Aprsis --- .../com/radio/codec2talkie/app/AppWorker.java | 15 +- .../connect/TcpIpConnectActivity.java | 11 +- .../com/radio/codec2talkie/protocol/Aprs.java | 2 +- .../radio/codec2talkie/protocol/AprsIs.java | 140 +++++++++++++++++- .../codec2talkie/protocol/aprs/AprsData.java | 2 +- .../protocol/aprs/AprsDataFactory.java | 4 +- .../protocol/aprs/AprsDataPositionReport.java | 3 +- .../aprs/AprsDataPositionReportMicE.java | 3 +- .../protocol/aprs/AprsDataTextMessage.java | 6 +- .../protocol/message/TextMessage.java | 1 + .../protocol/position/Position.java | 1 + .../radio/codec2talkie/tools/TextTools.java | 17 +++ 12 files changed, 182 insertions(+), 23 deletions(-) diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/app/AppWorker.java b/codec2talkie/src/main/java/com/radio/codec2talkie/app/AppWorker.java index 6f7747d..2d7f155 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/app/AppWorker.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/app/AppWorker.java @@ -9,7 +9,6 @@ import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.AudioTrack; -import android.media.MediaRecorder; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -64,7 +63,7 @@ public class AppWorker extends Thread { private short[] _recordAudioBuffer; // callbacks - private final Handler _onPlayerStateChanged; + private final Handler _onWorkerStateChanged; private Handler _onMessageReceived; private final Timer _processPeriodicTimer; @@ -80,8 +79,8 @@ public class AppWorker extends Thread { private final SharedPreferences _sharedPreferences; public AppWorker(TransportFactory.TransportType transportType, - Handler onPlayerStateChanged, Context context) throws IOException { - _onPlayerStateChanged = onPlayerStateChanged; + Handler onWorkerStateChanged, Context context) throws IOException { + _onWorkerStateChanged = onWorkerStateChanged; _context = context; _sharedPreferences = PreferenceManager.getDefaultSharedPreferences(_context); @@ -220,7 +219,7 @@ public class AppWorker extends Thread { if (note != null) { msg.obj = note; } - _onPlayerStateChanged.sendMessage(msg); + _onWorkerStateChanged.sendMessage(msg); } if (newStatus != AppMessage.EV_LISTENING) { restartListening(); @@ -232,21 +231,21 @@ public class AppWorker extends Thread { msg.what = AppMessage.EV_RX_RADIO_LEVEL.toInt(); msg.arg1 = rssi; msg.arg2 = snr; - _onPlayerStateChanged.sendMessage(msg); + _onWorkerStateChanged.sendMessage(msg); } private void sendRxAudioLevelUpdate(short [] pcmAudioSamples) { Message msg = Message.obtain(); msg.what = AppMessage.EV_RX_LEVEL.toInt(); msg.arg1 = AudioTools.getSampleLevelDb(pcmAudioSamples); - _onPlayerStateChanged.sendMessage(msg); + _onWorkerStateChanged.sendMessage(msg); } private void sendTxAudioLevelUpdate(short [] pcmAudioSamples) { Message msg = Message.obtain(); msg.what = AppMessage.EV_TX_LEVEL.toInt(); msg.arg1 = AudioTools.getSampleLevelDb(pcmAudioSamples); - _onPlayerStateChanged.sendMessage(msg); + _onWorkerStateChanged.sendMessage(msg); } private void recordAndSendAudioFrame() throws IOException { diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/connect/TcpIpConnectActivity.java b/codec2talkie/src/main/java/com/radio/codec2talkie/connect/TcpIpConnectActivity.java index fd09a2c..5abbfeb 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/connect/TcpIpConnectActivity.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/connect/TcpIpConnectActivity.java @@ -22,7 +22,6 @@ import com.radio.codec2talkie.R; import com.radio.codec2talkie.settings.PreferenceKeys; import java.io.IOException; -import java.net.ConnectException; import java.net.InetSocketAddress; import java.net.Socket; @@ -31,11 +30,11 @@ public class TcpIpConnectActivity extends AppCompatActivity { private final int TCP_IP_CONNECTED = 1; private final int TCP_IP_FAILED = 2; - private final int DEFAULT_MAX_RETRIES = 5; - private final int DEFAULT_RETRY_DELAY_MS = 5000; + private static final int DEFAULT_MAX_RETRIES = 5; + private static final int DEFAULT_RETRY_DELAY_MS = 5000; - private final String DEFAULT_ADDRESS = "127.0.0.1"; - private final String DEFAULT_PORT = "8081"; + private static final String DEFAULT_ADDRESS = "127.0.0.1"; + private static final String DEFAULT_PORT = "8081"; private String _address; private String _port; @@ -57,7 +56,7 @@ public class TcpIpConnectActivity extends AppCompatActivity { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); _address = sharedPreferences.getString(PreferenceKeys.PORTS_TCP_IP_ADDRESS, DEFAULT_ADDRESS); _port = sharedPreferences.getString(PreferenceKeys.PORTS_TCP_IP_PORT, DEFAULT_PORT); - _maxRetries = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.PORTS_TCP_IP_RETRY_COUNT, String.valueOf(DEFAULT_MAX_RETRIES)));; + _maxRetries = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.PORTS_TCP_IP_RETRY_COUNT, String.valueOf(DEFAULT_MAX_RETRIES))); _retryDelayMs = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.PORTS_TCP_IP_RETRY_DELAY, String.valueOf(DEFAULT_RETRY_DELAY_MS))); ProgressBar progressBarTcpIp = findViewById(R.id.progressBarTcpIp); diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/Aprs.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/Aprs.java index f4dc414..01cd6b9 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/Aprs.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/Aprs.java @@ -143,7 +143,7 @@ public class Aprs implements Protocol { protected void onReceiveData(String src, String dst, String path, byte[] data) { if (data.length == 0) return; AprsDataType dataType = new AprsDataType((char)data[0]); - AprsData aprsData = AprsDataFactory.fromBinary(src, dst, data); + AprsData aprsData = AprsDataFactory.fromBinary(src, dst, path, data); if (aprsData != null && aprsData.isValid()) { if (dataType.isTextMessage()) { TextMessage textMessage = aprsData.toTextMessage(); diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/AprsIs.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/AprsIs.java index 56ff5fb..2a578ac 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/AprsIs.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/AprsIs.java @@ -2,18 +2,36 @@ package com.radio.codec2talkie.protocol; import android.content.Context; import android.content.SharedPreferences; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; import android.util.Log; import androidx.preference.PreferenceManager; +import com.radio.codec2talkie.app.AppMessage; import com.radio.codec2talkie.protocol.message.TextMessage; import com.radio.codec2talkie.protocol.position.Position; import com.radio.codec2talkie.settings.PreferenceKeys; +import com.radio.codec2talkie.tools.DebugTools; +import com.radio.codec2talkie.tools.TextTools; +import com.radio.codec2talkie.transport.TcpIp; import com.radio.codec2talkie.transport.Transport; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Timer; +import java.util.TimerTask; -public class AprsIs implements Protocol { +public class AprsIs implements Protocol, Runnable { private static final String TAG = AprsIs.class.getSimpleName(); private final Protocol _childProtocol; @@ -27,11 +45,20 @@ public class AprsIs implements Protocol { private boolean _isRxGateEnabled; private boolean _isTxGateEnabled; + private String _callsign; + private String _ssid; private int _filterRadius; private String _filter; + private final ByteBuffer _rxQueue; + private final ByteBuffer _txQueue; + + protected boolean _isRunning = true; + public AprsIs(Protocol childProtocol) { _childProtocol = childProtocol; + _rxQueue = ByteBuffer.allocate(4096); + _txQueue = ByteBuffer.allocate(4096); } @Override @@ -43,12 +70,16 @@ public class AprsIs implements Protocol { _isRxGateEnabled = sharedPreferences.getBoolean(PreferenceKeys.APRS_IS_ENABLE_RX_GATE, false); _isTxGateEnabled = sharedPreferences.getBoolean(PreferenceKeys.APRS_IS_ENABLE_TX_GATE, false); _isSelfEnabled = sharedPreferences.getBoolean(PreferenceKeys.APRS_IS_ENABLE_SELF, false); + _callsign = sharedPreferences.getString(PreferenceKeys.AX25_CALLSIGN, "N0CALL"); + _ssid = sharedPreferences.getString(PreferenceKeys.AX25_SSID, "0"); _passcode = sharedPreferences.getString(PreferenceKeys.APRS_IS_CODE, ""); _server = sharedPreferences.getString(PreferenceKeys.APRS_IS_TCPIP_SERVER, "euro.aprs2.net"); _filterRadius = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.APRS_IS_RADIUS, "10")); _filter = sharedPreferences.getString(PreferenceKeys.APRS_IS_FILTER, ""); Log.i(TAG, "AprsIs RX gate: " + _isTxGateEnabled + ", TX gate: " + _isTxGateEnabled + ", server: " + _server); + + new Thread(this).start(); } @Override @@ -81,7 +112,17 @@ public class AprsIs implements Protocol { @Override public boolean receive() throws IOException { - return _childProtocol.receive(); + String line = ""; + synchronized (_rxQueue) { + line = TextTools.getString(_rxQueue); + } + if (line.length() > 0) { + if (_isTxGateEnabled) { + // TODO, forward APRS-IS data to radio + } + _parentProtocolCallback.onReceiveLog(line); + } + return _childProtocol.receive() || line.length() > 0; } ProtocolCallback _protocolCallback = new ProtocolCallback() { @@ -176,6 +217,101 @@ public class AprsIs implements Protocol { @Override public void close() { + Log.i(TAG, "close()"); + _isRunning = false; _childProtocol.close(); } + + private String getLoginCommand() { + String cmd = "user " + _callsign + "-" + _ssid + " pass " + _passcode + " vers " + "C2T 1.0"; + if (_filterRadius > 0) { + cmd += " filter m/" + _filterRadius; + } + if (_filter.length() > 0) { + if (!cmd.contains("filter")) { + cmd += " filter "; + } + cmd += " " + _filter; + } + cmd += "\n"; + return cmd; + } + + @Override + public void run() { + Socket socket; + boolean isConnected = false; + TcpIp tcpIp = null; + byte[] buf = new byte[4096]; + + Log.i(TAG, "Started APRS-IS thread"); + while (_isRunning) { + // connect + if (!isConnected) { + socket = new Socket(); + try { + socket.connect(new InetSocketAddress(_server, 14580)); + tcpIp = new TcpIp(socket, "aprsis"); + String loginCmd = getLoginCommand(); + Log.i(TAG, "Login command " + loginCmd); + tcpIp.write(loginCmd.getBytes()); + Log.i(TAG, "Connected to " + _server); + } catch (IOException e) { + Log.w(TAG, "Failed to connect"); + e.printStackTrace(); + try { + Thread.sleep(10000); + } catch (InterruptedException interruptedException) { + interruptedException.printStackTrace(); + } + isConnected = false; + continue; + } + isConnected = true; + } + // read data + int bytesRead = 0; + try { + // # aprsc 2.1.11-g80df3b4 20 Aug 2022 11:33:40 GMT T2FINLAND 85.188.1.129:14580 + // # logresp N0CALL unverified, server T2GYOR<0xd><0xa> + bytesRead = tcpIp.read(buf); + } catch (IOException e) { + // TODO, notify parent + Log.w(TAG, "Lost connection on receive"); + e.printStackTrace(); + isConnected = false; + continue; + } + if (bytesRead > 0 && buf[0] != '#') { + synchronized (_rxQueue) { + _rxQueue.put(Arrays.copyOf(buf, bytesRead)); + } + } + // write data + synchronized (_txQueue) { + String line = TextTools.getString(_txQueue); + if (line.length() > 0) { + Log.v(TAG, line); + try { + tcpIp.write(line.getBytes()); + } catch (IOException e) { + // TODO, notify parent + Log.w(TAG, "Lost connection on transmit"); + e.printStackTrace(); + isConnected = false; + } + } + } + } + + if (tcpIp != null) { + try { + tcpIp.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + Log.i(TAG, "Stopped APRS-IS thread"); + } } diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsData.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsData.java index 8a62bc5..d3fad15 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsData.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsData.java @@ -8,7 +8,7 @@ public interface AprsData { void fromTextMessage(TextMessage textMessage); Position toPosition(); TextMessage toTextMessage(); - void fromBinary(String srcCallsign, String dstCallsign, byte[] infoData); + void fromBinary(String srcCallsign, String dstCallsign, String digipath, byte[] infoData); byte[] toBinary(); boolean isValid(); } diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataFactory.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataFactory.java index 4321146..39e8eda 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataFactory.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataFactory.java @@ -20,14 +20,14 @@ public class AprsDataFactory { return null; } - public static AprsData fromBinary(String srcCallsign, String dstCallsign, byte[] infoData) { + public static AprsData fromBinary(String srcCallsign, String dstCallsign, String digipath, byte[] infoData) { ByteBuffer buffer = ByteBuffer.wrap(infoData); AprsDataType dataType = new AprsDataType((char)buffer.get()); AprsData aprsData = create(dataType); if (aprsData == null) return null; byte[] data = new byte[buffer.remaining()]; buffer.get(data); - aprsData.fromBinary(srcCallsign, dstCallsign, data); + aprsData.fromBinary(srcCallsign, dstCallsign, digipath, data); return aprsData; } } diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataPositionReport.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataPositionReport.java index aa04dc1..f28414b 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataPositionReport.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataPositionReport.java @@ -48,11 +48,12 @@ public class AprsDataPositionReport implements AprsData { } @Override - public void fromBinary(String srcCallsign, String dstCallsign, byte[] infoData) { + public void fromBinary(String srcCallsign, String dstCallsign, String digipath, byte[] infoData) { _isValid = false; _position = new Position(); _position.srcCallsign = srcCallsign; _position.dstCallsign = dstCallsign; + _position.digipath = digipath; _position.status = ""; _position.comment = ""; _position.privacyLevel = 0; diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataPositionReportMicE.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataPositionReportMicE.java index 3630066..2056165 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataPositionReportMicE.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataPositionReportMicE.java @@ -122,12 +122,13 @@ public class AprsDataPositionReportMicE implements AprsData { } @Override - public void fromBinary(String srcCallsign, String dstCallsign, byte[] infoData) { + public void fromBinary(String srcCallsign, String dstCallsign, String digipath, byte[] infoData) { _isValid = false; _position = new Position(); _dstCallsign = dstCallsign; _position.srcCallsign = srcCallsign; _position.dstCallsign = dstCallsign; + _position.digipath = digipath; _position.privacyLevel = 0; _position.status = ""; _position.comment = ""; diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataTextMessage.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataTextMessage.java index aedb9c1..d5bf1c8 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataTextMessage.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataTextMessage.java @@ -9,6 +9,7 @@ public class AprsDataTextMessage implements AprsData { public String srcCallsign; public String dstCallsign; + public String digipath; public String textMessage; private boolean _isValid; @@ -22,6 +23,7 @@ public class AprsDataTextMessage implements AprsData { public void fromTextMessage(TextMessage textMessage) { this.dstCallsign = textMessage.dst; this.textMessage = textMessage.text; + this.digipath = textMessage.digipath; _isValid = true; } @@ -35,14 +37,16 @@ public class AprsDataTextMessage implements AprsData { TextMessage textMessage = new TextMessage(); textMessage.src = this.srcCallsign; textMessage.dst = this.dstCallsign; + textMessage.digipath = this.digipath; textMessage.text = this.textMessage; return textMessage; } @Override - public void fromBinary(String srcCallsign, String dstCallsign, byte[] infoData) { + public void fromBinary(String srcCallsign, String dstCallsign, String digipath, byte[] infoData) { _isValid = false; if (infoData.length < 10) return; + this.digipath = digipath; this.srcCallsign = srcCallsign; ByteBuffer buffer = ByteBuffer.wrap(infoData); // callsign, trim ending spaces diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/message/TextMessage.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/message/TextMessage.java index a184985..dcd18be 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/message/TextMessage.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/message/TextMessage.java @@ -5,6 +5,7 @@ import com.radio.codec2talkie.storage.message.MessageItem; public class TextMessage { public String src; public String dst; + public String digipath; public String text; public MessageItem toMessageItem(boolean isTransmit) { diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/position/Position.java b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/position/Position.java index 3f6a4d0..2dc80a3 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/position/Position.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/position/Position.java @@ -9,6 +9,7 @@ public class Position { public long timestampEpochMs; public String srcCallsign; public String dstCallsign; + public String digipath; public double latitude; public double longitude; public String maidenHead; diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/tools/TextTools.java b/codec2talkie/src/main/java/com/radio/codec2talkie/tools/TextTools.java index b598364..37f7b04 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/tools/TextTools.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/tools/TextTools.java @@ -2,6 +2,7 @@ package com.radio.codec2talkie.tools; import android.util.Log; +import java.nio.ByteBuffer; import java.util.Arrays; public class TextTools { @@ -35,4 +36,20 @@ public class TextTools { if (i == data.length) return data; return Arrays.copyOf(data, i); } + + public static String getString(ByteBuffer byteBuffer) { + StringBuilder result = new StringBuilder(); + if (byteBuffer.position() > 0) { + byteBuffer.flip(); + while (byteBuffer.hasRemaining()) { + char c = (char)byteBuffer.get(); + if (c == '\n') { + break; + } + result.append(c); + } + byteBuffer.compact(); + } + return result.toString(); + } }