kopia lustrzana https://github.com/ge0rg/aprsdroid
DigiRig Support
rodzic
28b0fd9140
commit
b98c58983c
|
@ -36,6 +36,14 @@
|
|||
<item>usb</item>
|
||||
<item>tcpip</item>
|
||||
</string-array>
|
||||
<string-array name="p_afsk_e">
|
||||
<item>@string/p_afsk_vox</item>
|
||||
<item>@string/p_afsk_digirig</item>
|
||||
</string-array>
|
||||
<string-array name="p_afsk_ev">
|
||||
<item>vox</item>
|
||||
<item>digirig</item>
|
||||
</string-array>
|
||||
<string-array name="p_afsk_out_ev">
|
||||
<item>0</item>
|
||||
<item>2</item>
|
||||
|
|
|
@ -243,6 +243,9 @@
|
|||
<string name="p_link_ble">Bluetooth Low Energy</string>
|
||||
<string name="p_link_tcpip">TCP/IP</string>
|
||||
<string name="p_link_usb">USB Serial</string>
|
||||
<!-- array of AFSK modes -->
|
||||
<string name="p_afsk_vox">VOX</string>
|
||||
<string name="p_afsk_digirig">Digirig</string>
|
||||
<!-- array of location sources -->
|
||||
<string name="p_source_manual">Manual Position</string>
|
||||
<string name="p_source_periodic">Periodic GPS/Network Position</string>
|
||||
|
@ -397,6 +400,8 @@
|
|||
<string name="p_afsk_btsco">Bluetooth Headset</string>
|
||||
<string name="p_afsk_btsco_summary">Use Bluetooth (SCO) headset for AFSK</string>
|
||||
<string name="p_afsk_output">Audio Output</string>
|
||||
<string name="p_afsk_ptt">Use Push-to-Talk</string>
|
||||
<string name="p_afsk_pttport">Push-to-Talk Port</string>
|
||||
<string-array name="p_afsk_out_e">
|
||||
<item>Voice Call</item>
|
||||
<item>Ringtone</item>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/p_link_usb">
|
||||
|
||||
<de.duenndns.ListPreferenceWithValue
|
||||
android:key="baudrate"
|
||||
android:title="@string/p_serial_baudrate"
|
||||
android:summary="@string/p_serial_baudrate_summary"
|
||||
android:entries="@array/p_serial_baudrates"
|
||||
android:entryValues="@array/p_serial_baudrates"
|
||||
android:defaultValue="115200"
|
||||
android:dialogTitle="@string/p_serial_baudrate" />
|
||||
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
|
@ -38,7 +38,14 @@
|
|||
android:summary="@string/p_afsk_prefix_summary"
|
||||
android:dialogTitle="@string/p_afsk_prefix_entry" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<de.duenndns.ListPreferenceWithValue
|
||||
android:defaultValue="vox"
|
||||
android:dialogTitle="@string/p_link_entry"
|
||||
android:entries="@array/p_afsk_e"
|
||||
android:entryValues="@array/p_afsk_ev"
|
||||
android:key="afsk"
|
||||
android:title="@string/p_link" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ class BackendPrefs extends PreferenceActivity
|
|||
}
|
||||
|
||||
override def onSharedPreferenceChanged(sp: SharedPreferences, key : String) {
|
||||
if (key == "proto" || key == "link" || key == "aprsis") {
|
||||
if (key == "proto" || key == "link" || key == "aprsis" || key == "afsk") {
|
||||
setPreferenceScreen(null)
|
||||
loadXml()
|
||||
}
|
||||
|
|
|
@ -92,6 +92,7 @@ class PrefsWrapper(val context : Context) {
|
|||
R.array.p_conntype_ev, R.array.p_conntype_e)
|
||||
val link = AprsBackend.defaultProtoInfo(this).link
|
||||
link match {
|
||||
case "afsk" => "%s, %s".format(proto, getListItemName(link, AprsBackend.DEFAULT_CONNTYPE, R.array.p_afsk_ev, R.array.p_afsk_e))
|
||||
case "aprsis" => "%s, %s".format(proto, getListItemName(link, AprsBackend.DEFAULT_CONNTYPE, R.array.p_aprsis_ev, R.array.p_aprsis_e))
|
||||
case "link" => "%s, %s".format(proto, getListItemName(link, AprsBackend.DEFAULT_CONNTYPE, R.array.p_link_ev, R.array.p_link_e))
|
||||
case _ => proto
|
||||
|
@ -120,6 +121,8 @@ class PrefsWrapper(val context : Context) {
|
|||
|
||||
def getProto() = getString("proto", "aprsis")
|
||||
def getAfskHQ() = getBoolean("afsk.hqdemod", true)
|
||||
def getAfskRTS() = getBoolean("afsk.ptt", false)
|
||||
def getPTTPort() = getString("afsk.pttport", "")
|
||||
def getAfskBluetooth() = getBoolean("afsk.btsco", false) && getAfskHQ()
|
||||
def getAfskOutput() = if (getAfskBluetooth()) AudioManager.STREAM_VOICE_CALL else getStringInt("afsk.output", 0)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
|
||||
package org.aprsdroid.app
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import _root_.android.util.Log
|
||||
import _root_.net.ab0oo.aprs.parser.APRSPacket
|
||||
|
||||
import _root_.java.io.{InputStream, OutputStream}
|
||||
|
||||
object AprsBackend {
|
||||
val TAG = "AprsBackend"
|
||||
/** "Modular" system to connect to an APRS backend.
|
||||
* The backend config consists of three items backed by prefs values:
|
||||
* - *proto* inside the connection ("aprsis", "afsk", "kiss", "tnc2", "kenwood") - ProtoInfo class
|
||||
|
@ -65,7 +69,7 @@ object AprsBackend {
|
|||
Set(),
|
||||
CAN_XMIT,
|
||||
PASSCODE_REQUIRED),
|
||||
"afsk" -> new BackendInfo(
|
||||
"vox" -> new BackendInfo(
|
||||
(s, p) => new AfskUploader(s, p),
|
||||
0,
|
||||
Set(Manifest.permission.RECORD_AUDIO),
|
||||
|
@ -83,12 +87,6 @@ object AprsBackend {
|
|||
Set(BLUETOOTH_PERMISSION),
|
||||
CAN_DUPLEX,
|
||||
PASSCODE_NONE),
|
||||
"ble" -> new BackendInfo(
|
||||
(s, p) => new BluetoothLETnc(s, p),
|
||||
R.xml.backend_ble,
|
||||
Set(BLUETOOTH_PERMISSION),
|
||||
CAN_DUPLEX,
|
||||
PASSCODE_NONE),
|
||||
"tcpip" -> new BackendInfo(
|
||||
(s, p) => new TcpUploader(s, p),
|
||||
R.xml.backend_tcptnc,
|
||||
|
@ -100,7 +98,14 @@ object AprsBackend {
|
|||
R.xml.backend_usb,
|
||||
Set(),
|
||||
CAN_DUPLEX,
|
||||
PASSCODE_NONE)
|
||||
PASSCODE_NONE),
|
||||
"digirig" -> new BackendInfo(
|
||||
(s, p) => new DigiRig(s, p),
|
||||
R.xml.backend_digirig,
|
||||
Set(Manifest.permission.RECORD_AUDIO),
|
||||
CAN_DUPLEX,
|
||||
PASSCODE_NONE
|
||||
)
|
||||
)
|
||||
|
||||
class ProtoInfo(
|
||||
|
@ -114,8 +119,8 @@ object AprsBackend {
|
|||
(s, is, os) => new AprsIsProto(s, is, os),
|
||||
R.xml.proto_aprsis, "aprsis"),
|
||||
"afsk" -> new ProtoInfo(
|
||||
null,
|
||||
R.xml.proto_afsk, null),
|
||||
(s, is, os) => new AfskProto(s, is, os),
|
||||
R.xml.proto_afsk, "afsk"),
|
||||
"kiss" -> new ProtoInfo(
|
||||
(s, is, os) => new KissProto(s, is, os),
|
||||
R.xml.proto_kiss, "link"),
|
||||
|
@ -136,7 +141,15 @@ object AprsBackend {
|
|||
|
||||
def defaultBackendInfo(prefs : PrefsWrapper) : BackendInfo = {
|
||||
val pi = defaultProtoInfo(prefs)
|
||||
val link = if (pi.link != null) { prefs.getString(pi.link, DEFAULT_LINK) } else { prefs.getProto() }
|
||||
var link = ""
|
||||
if (pi.link != null) {
|
||||
link = prefs.getString(pi.link, DEFAULT_LINK)
|
||||
Log.d(TAG, "DEBUG: pi.link (" + pi.link + ") != null : " + link)
|
||||
} else {
|
||||
link = prefs.getProto()
|
||||
Log.d(TAG, "DEBUG: pi.link == null : " + link)
|
||||
}
|
||||
|
||||
backend_collection.get(link) match {
|
||||
case Some(bi) => bi
|
||||
case None => backend_collection(DEFAULT_CONNTYPE)
|
||||
|
|
|
@ -0,0 +1,257 @@
|
|||
package org.aprsdroid.app
|
||||
|
||||
import _root_.android.media.{AudioManager, AudioTrack}
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
import android.hardware.usb.UsbManager
|
||||
import android.hardware.usb.UsbDevice
|
||||
import android.hardware.usb.UsbDeviceConnection
|
||||
import android.media.AudioTrack.OnPlaybackPositionUpdateListener
|
||||
import android.util.Log
|
||||
import java.io.{InputStream, OutputStream}
|
||||
|
||||
import net.ab0oo.aprs.parser._
|
||||
import com.nogy.afu.soundmodem.{Message, APRSFrame, Afsk}
|
||||
import com.felhr.usbserial._
|
||||
import com.jazzido.PacketDroid.{AudioBufferProcessor, PacketCallback}
|
||||
import sivantoledo.ax25.PacketHandler
|
||||
|
||||
object DigiRig {
|
||||
def deviceHandle(dev : UsbDevice) = {
|
||||
"usb_%04x_%04x_%s".format(dev.getVendorId(), dev.getProductId(), dev.getDeviceName())
|
||||
}
|
||||
|
||||
def checkDeviceHandle(prefs : SharedPreferences, dev_p : android.os.Parcelable) : Boolean = {
|
||||
if (dev_p == null)
|
||||
return false
|
||||
val dev = dev_p.asInstanceOf[UsbDevice]
|
||||
val last_use = prefs.getString(deviceHandle(dev), null)
|
||||
if (last_use == null)
|
||||
return false
|
||||
prefs.edit().putString("proto", last_use)
|
||||
.putString("link", "usb").commit()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
class DigiRig(service : AprsService, prefs : PrefsWrapper) extends AfskUploader(service, prefs)
|
||||
with PacketHandler with PacketCallback {
|
||||
override val TAG = "APRSdroid.Digirig"
|
||||
|
||||
// USB stuff
|
||||
val USB_PERM_ACTION = "org.aprsdroid.app.DigiRig.PERM"
|
||||
val ACTION_USB_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
val ACTION_USB_DETACHED = "android.hardware.usb.action.USB_DEVICE_DETACHED"
|
||||
|
||||
val usbManager = service.getSystemService(Context.USB_SERVICE).asInstanceOf[UsbManager];
|
||||
var thread : UsbThread = null
|
||||
var dev : UsbDevice = null
|
||||
var con : UsbDeviceConnection = null
|
||||
var ser : UsbSerialInterface = null
|
||||
var alreadyRunning = false
|
||||
|
||||
val intent = new Intent(USB_PERM_ACTION)
|
||||
val pendingIntent = PendingIntent.getBroadcast(service, 0, intent, PendingIntent.FLAG_MUTABLE)
|
||||
|
||||
// Audio stuff
|
||||
var audioPlaying = false
|
||||
output.setVolume(AudioTrack.getMaxVolume())
|
||||
output.setPlaybackPositionUpdateListener(new OnPlaybackPositionUpdateListener {
|
||||
override def onMarkerReached(audioTrack: AudioTrack): Unit = {
|
||||
DigiRig.this.audioPlaying = false
|
||||
}
|
||||
|
||||
override def onPeriodicNotification(audioTrack: AudioTrack): Unit = {}
|
||||
})
|
||||
|
||||
val receiver = new BroadcastReceiver() {
|
||||
override def onReceive(ctx: Context, i: Intent) {
|
||||
Log.d(TAG, "onReceive: " + i)
|
||||
if (i.getAction() == ACTION_USB_DETACHED) {
|
||||
log("USB device detached.")
|
||||
ctx.stopService(AprsService.intent(ctx, AprsService.SERVICE))
|
||||
return
|
||||
}
|
||||
val granted = i.getExtras().getBoolean(UsbManager.EXTRA_PERMISSION_GRANTED)
|
||||
if (!granted) {
|
||||
service.postAbort(service.getString(R.string.p_serial_noperm))
|
||||
return
|
||||
}
|
||||
log("Obtained USB permissions.")
|
||||
thread = new UsbThread()
|
||||
thread.start()
|
||||
}
|
||||
}
|
||||
|
||||
override val btScoReceiver = new BroadcastReceiver() {
|
||||
override def onReceive(ctx : Context, i : Intent) {
|
||||
Log.d(TAG, "onReceive: " + i)
|
||||
if (i.getAction() == ACTION_USB_DETACHED) {
|
||||
log("USB device detached.")
|
||||
ctx.stopService(AprsService.intent(ctx, AprsService.SERVICE))
|
||||
return
|
||||
}
|
||||
val granted = i.getExtras().getBoolean(UsbManager.EXTRA_PERMISSION_GRANTED)
|
||||
if (!granted) {
|
||||
service.postAbort(service.getString(R.string.p_serial_noperm))
|
||||
return
|
||||
}
|
||||
log("Obtained USB permissions.")
|
||||
thread = new UsbThread()
|
||||
thread.start()
|
||||
|
||||
val state = i.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1)
|
||||
Log.d(TAG, "AudioManager SCO event: " + state)
|
||||
if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
|
||||
// we are connected, perform actual start
|
||||
log(service.getString(R.string.afsk_info_sco_est))
|
||||
aw.start()
|
||||
service.unregisterReceiver(this)
|
||||
service.postPosterStarted()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var proto : TncProto = null
|
||||
var sis : SerialInputStream = null
|
||||
|
||||
override def start() = {
|
||||
val filter = new IntentFilter(USB_PERM_ACTION)
|
||||
filter.addAction(ACTION_USB_DETACHED)
|
||||
service.registerReceiver(receiver, filter)
|
||||
alreadyRunning = true
|
||||
if (ser == null)
|
||||
requestPermissions()
|
||||
|
||||
if (!isCallsignAX25Valid())
|
||||
false
|
||||
|
||||
if (use_bt) {
|
||||
log(service.getString(R.string.afsk_info_sco_req))
|
||||
service.getSystemService(Context.AUDIO_SERVICE)
|
||||
.asInstanceOf[AudioManager].startBluetoothSco()
|
||||
service.registerReceiver(btScoReceiver, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED))
|
||||
false
|
||||
} else {
|
||||
aw.start()
|
||||
true
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
def requestPermissions() {
|
||||
Log.d(TAG, "Digirig.requestPermissions");
|
||||
val dl = usbManager.getDeviceList();
|
||||
var requested = false
|
||||
import scala.collection.JavaConversions._
|
||||
for ((name, dev) <- dl) {
|
||||
val deviceVID = dev.getVendorId()
|
||||
val devicePID = dev.getProductId()
|
||||
if (UsbSerialDevice.isSupported(dev)) {
|
||||
// this is not a USB Hub
|
||||
log("Found USB device %04x:%04x, requesting permissions.".format(deviceVID, devicePID))
|
||||
this.dev = dev
|
||||
usbManager.requestPermission(dev, pendingIntent)
|
||||
return
|
||||
} else
|
||||
log("Unsupported USB device %04x:%04x.".format(deviceVID, devicePID))
|
||||
}
|
||||
service.postAbort(service.getString(R.string.p_serial_notfound))
|
||||
}
|
||||
|
||||
override def update(packet: APRSPacket): String = {
|
||||
// Need to "parse" the packet in order to replace the Digipeaters
|
||||
packet.setDigipeaters(Digipeater.parseList(Digis, true))
|
||||
val from = packet.getSourceCall()
|
||||
val to = packet.getDestinationCall()
|
||||
val data = packet.getAprsInformation().toString()
|
||||
val msg = new APRSFrame(from, to, Digis, data, FrameLength).getMessage()
|
||||
Log.d(TAG, "update(): From: " + from + " To: " + to + " Via: " + Digis + " telling " + data)
|
||||
|
||||
ser.setRTS(true)
|
||||
audioPlaying = true
|
||||
val result = sendMessage(msg)
|
||||
while (audioPlaying) {
|
||||
Thread.sleep(10)
|
||||
}
|
||||
ser.setRTS(false)
|
||||
|
||||
if (result)
|
||||
"AFSK OK"
|
||||
else
|
||||
"AFSK busy"
|
||||
}
|
||||
|
||||
override def stop() {
|
||||
// Stop USB thread
|
||||
if (alreadyRunning)
|
||||
service.unregisterReceiver(receiver)
|
||||
alreadyRunning = false
|
||||
if (ser != null)
|
||||
ser.close()
|
||||
if (sis != null)
|
||||
sis.close()
|
||||
if (con != null)
|
||||
con.close()
|
||||
if (thread == null)
|
||||
return
|
||||
thread.synchronized {
|
||||
thread.running = false
|
||||
}
|
||||
thread.interrupt()
|
||||
thread.join(50)
|
||||
|
||||
// Stop AFSK Demodulator
|
||||
aw.close()
|
||||
if (use_bt) {
|
||||
service.getSystemService(Context.AUDIO_SERVICE)
|
||||
.asInstanceOf[AudioManager].stopBluetoothSco()
|
||||
try {
|
||||
service.unregisterReceiver(btScoReceiver)
|
||||
} catch {
|
||||
case e : RuntimeException => // ignore, receiver already unregistered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UsbThread() extends Thread("APRSdroid USB connection") {
|
||||
val TAG = "UsbThread"
|
||||
var running = true
|
||||
|
||||
def log(s : String) {
|
||||
service.postAddPost(StorageDatabase.Post.TYPE_INFO, R.string.post_info, s)
|
||||
}
|
||||
|
||||
override def run() {
|
||||
val con = usbManager.openDevice(dev)
|
||||
ser = UsbSerialDevice.createUsbSerialDevice(dev, con)
|
||||
if (ser == null || !ser.syncOpen()) {
|
||||
con.close()
|
||||
service.postAbort(service.getString(R.string.p_serial_unsupported))
|
||||
return
|
||||
}
|
||||
val baudrate = prefs.getStringInt("baudrate", 115200)
|
||||
ser.setBaudRate(baudrate)
|
||||
ser.setDataBits(UsbSerialInterface.DATA_BITS_8)
|
||||
ser.setStopBits(UsbSerialInterface.STOP_BITS_1)
|
||||
ser.setParity(UsbSerialInterface.PARITY_NONE)
|
||||
ser.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF)
|
||||
ser.setRTS(false)
|
||||
|
||||
// success: remember this for usb-attach launch
|
||||
prefs.prefs.edit().putString(UsbTnc.deviceHandle(dev), prefs.getString("proto", "afsk")).commit()
|
||||
|
||||
log("Opened " + ser.getClass().getSimpleName() + " at " + baudrate + "bd")
|
||||
service.postPosterStarted()
|
||||
while (running) { /* do nothing */ }
|
||||
Log.d(TAG, "terminate()")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.aprsdroid.app
|
||||
|
||||
import _root_.android.util.Log
|
||||
import _root_.java.io.{InputStream, OutputStream}
|
||||
|
||||
import _root_.net.ab0oo.aprs.parser._
|
||||
|
||||
class AfskProto(service : AprsService, is : InputStream, os : OutputStream) extends TncProto(is, os) {
|
||||
val TAG = "APRSdroid.AfskProto"
|
||||
|
||||
def readPacket() : String = {
|
||||
""
|
||||
}
|
||||
|
||||
def writePacket(p : APRSPacket) {
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue