kopia lustrzana https://github.com/ge0rg/aprsdroid
Merge branch 'bluetooth_tnc' into master
commit
7021857c5d
|
|
@ -9,6 +9,7 @@
|
|||
android:targetSdkVersion="8" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
|
||||
|
|
|
|||
Plik binarny nie jest wyświetlany.
|
|
@ -5,12 +5,14 @@
|
|||
<item>@string/p_conn_udp</item>
|
||||
<item>@string/p_conn_http</item>
|
||||
<item>@string/p_conn_afsk</item>
|
||||
<item>@string/p_conn_bt</item>
|
||||
</string-array>
|
||||
<string-array name="p_conntype_ev">
|
||||
<item>tcp</item>
|
||||
<item>udp</item>
|
||||
<item>http</item>
|
||||
<item>afsk</item>
|
||||
<item>bluetooth</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="p_gps_e">
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@
|
|||
<string name="p_conn_udp">UDP (send only)</string>
|
||||
<string name="p_conn_http">HTTP POST (send only)</string>
|
||||
<string name="p_conn_afsk">AFSK via Speaker</string>
|
||||
<string name="p_conn_bt">Bluetooth TNC</string>
|
||||
|
||||
<string name="p_host">Server</string>
|
||||
<string name="p_host_summary">APRS-IS server (port 8080) to send beacons</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="Bluetooth">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="bt.client"
|
||||
android:title="Client Mode"
|
||||
android:defaultValue="true"
|
||||
android:summary="Keep this on!" />
|
||||
|
||||
<de.duenndns.BluetoothDevicePreference
|
||||
android:key="bt.mac"
|
||||
android:dependency="bt.client"
|
||||
android:title="TNC Bluetooth Device"
|
||||
android:summary="You need to pair it from the system preferences"
|
||||
android:dialogTitle="choose device" />
|
||||
<EditTextPreference
|
||||
android:key="bt.channel"
|
||||
android:dependency="bt.client"
|
||||
android:inputType="number"
|
||||
android:title="Channel"
|
||||
android:summary="Usually this is '1'"
|
||||
android:dialogTitle="enter your channel" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="digi_path"
|
||||
android:hint="hop1,hop2,.."
|
||||
android:defaultValue="WIDE1-1"
|
||||
android:title="@string/p_aprs_path"
|
||||
android:summary="@string/p_aprs_path_summary"
|
||||
android:dialogTitle="@string/p_aprs_path_entry" />
|
||||
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package de.duenndns;
|
||||
|
||||
import android.bluetooth.*;
|
||||
import android.content.Context;
|
||||
import android.preference.ListPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class BluetoothDevicePreference extends ListPreference {
|
||||
|
||||
public BluetoothDevicePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
BluetoothAdapter bta = BluetoothAdapter.getDefaultAdapter();
|
||||
Set<BluetoothDevice> pairedDevices = bta.getBondedDevices();
|
||||
CharSequence[] entries = new CharSequence[pairedDevices.size()];
|
||||
CharSequence[] entryValues = new CharSequence[pairedDevices.size()];
|
||||
int i = 0;
|
||||
for (BluetoothDevice dev : pairedDevices) {
|
||||
entries[i] = dev.getName();
|
||||
entryValues[i] = dev.getAddress();
|
||||
i++;
|
||||
}
|
||||
setEntries(entries);
|
||||
setEntryValues(entryValues);
|
||||
}
|
||||
|
||||
public BluetoothDevicePreference(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,7 +22,11 @@ object Backend {
|
|||
"tcp" -> new BackendInfo(
|
||||
(s, p) => new TcpUploader(s, p),
|
||||
R.xml.pref_tcp,
|
||||
PASSCODE_OPTIONAL)
|
||||
PASSCODE_OPTIONAL),
|
||||
"bluetooth" -> new BackendInfo(
|
||||
(s, p) => new BluetoothTnc(s, p),
|
||||
R.xml.pref_bluetooth,
|
||||
PASSCODE_NONE)
|
||||
)
|
||||
|
||||
def defaultBackendInfo(prefs : PrefsWrapper) : BackendInfo = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
package org.aprsdroid.app
|
||||
|
||||
import _root_.android.bluetooth._
|
||||
import _root_.android.app.Service
|
||||
import _root_.android.content.Intent
|
||||
import _root_.android.location.Location
|
||||
import _root_.android.util.Log
|
||||
import _root_.java.io.{InputStream, OutputStream}
|
||||
import _root_.java.net.{InetAddress, Socket}
|
||||
import _root_.java.util.UUID
|
||||
|
||||
import _root_.net.ab0oo.aprs.parser._
|
||||
|
||||
class BluetoothTnc(service : AprsService, prefs : PrefsWrapper) extends AprsIsUploader(prefs) {
|
||||
val TAG = "APRSdroid.Bluetooth"
|
||||
val SPP = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
|
||||
|
||||
val bt_client = prefs.getBoolean("bt.client", true)
|
||||
val tncmac = prefs.getString("bt.mac", null)
|
||||
val tncchannel = prefs.getStringInt("bt.channel", -1)
|
||||
var digipath = prefs.getString("digi_path", "WIDE1-1")
|
||||
var conn : BtSocketThread = null
|
||||
|
||||
createConnection()
|
||||
|
||||
def start() {
|
||||
}
|
||||
|
||||
def createConnection() {
|
||||
Log.d(TAG, "BluetoothTnc.createConnection: " + tncmac)
|
||||
val adapter = BluetoothAdapter.getDefaultAdapter()
|
||||
if (adapter == null) {
|
||||
service.postAbort("Bluetooth not supported!")
|
||||
return
|
||||
}
|
||||
if (!adapter.isEnabled()) {
|
||||
service.postAbort("Bluetooth not enabled!")
|
||||
return
|
||||
}
|
||||
|
||||
val tnc = if (bt_client) adapter.getRemoteDevice(tncmac) else null
|
||||
conn = new BtSocketThread(adapter, tnc)
|
||||
conn.start()
|
||||
}
|
||||
|
||||
def update(packet : APRSPacket) : String = {
|
||||
packet.setDigipeaters(Digipeater.parseList(digipath, true))
|
||||
Log.d(TAG, "BluetoothTnc.update: " + packet)
|
||||
conn.update(packet)
|
||||
}
|
||||
|
||||
def stop() {
|
||||
if (conn == null)
|
||||
return
|
||||
conn.shutdown()
|
||||
conn.interrupt()
|
||||
conn.join(50)
|
||||
}
|
||||
|
||||
class BtSocketThread(ba : BluetoothAdapter, tnc : BluetoothDevice)
|
||||
extends Thread("APRSdroid Bluetooth connection") {
|
||||
val TAG = "BtSocketThread"
|
||||
var running = false
|
||||
var socket : BluetoothSocket = null
|
||||
var reader : KissReader = null
|
||||
var writer : KissWriter = null
|
||||
|
||||
def init_socket() {
|
||||
Log.d(TAG, "init_socket()")
|
||||
if (socket != null) {
|
||||
shutdown()
|
||||
}
|
||||
if (tnc == null) {
|
||||
// we are a host
|
||||
Log.d(TAG, "awaiting client connection...")
|
||||
socket = ba.listenUsingRfcommWithServiceRecord("SPP", SPP).accept(-1)
|
||||
Log.d(TAG, "client connected.")
|
||||
} else
|
||||
if (tncchannel == -1) {
|
||||
Log.d(TAG, "Connecting to SPP service...")
|
||||
socket = tnc.createRfcommSocketToServiceRecord(SPP)
|
||||
socket.connect()
|
||||
} else {
|
||||
Log.d(TAG, "Connecting to channel %d...".format(tncchannel))
|
||||
val m = tnc.getClass().getMethod("createRfcommSocket", classOf[Int])
|
||||
socket = m.invoke(tnc, tncchannel.asInstanceOf[AnyRef]).asInstanceOf[BluetoothSocket]
|
||||
socket.connect()
|
||||
}
|
||||
|
||||
this.synchronized {
|
||||
reader = new KissReader(socket.getInputStream())
|
||||
writer = new KissWriter(socket.getOutputStream())
|
||||
running = true
|
||||
}
|
||||
Log.d(TAG, "init_socket() done")
|
||||
}
|
||||
|
||||
override def run() {
|
||||
Log.d(TAG, "BtSocketThread.run()")
|
||||
try {
|
||||
init_socket()
|
||||
} catch {
|
||||
case e : Exception => e.printStackTrace(); service.postAbort(e.toString())
|
||||
}
|
||||
while (running) {
|
||||
try {
|
||||
Log.d(TAG, "waiting for data...")
|
||||
while (running) {
|
||||
val line = reader.readPacket()
|
||||
Log.d(TAG, "recv: " + line)
|
||||
service.postSubmit(line)
|
||||
}
|
||||
} catch {
|
||||
case e : Exception =>
|
||||
e.printStackTrace()
|
||||
Log.d(TAG, "reconnecting in 3s")
|
||||
try {
|
||||
Thread.sleep(3*1000)
|
||||
init_socket()
|
||||
} catch { case _ => }
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "BtSocketThread.terminate()")
|
||||
}
|
||||
|
||||
def update(packet : APRSPacket) : String = {
|
||||
if (socket != null) {
|
||||
writer.writePacket(packet.toAX25Frame())
|
||||
"Bluetooth OK"
|
||||
} else "Bluetooth disconnected"
|
||||
}
|
||||
|
||||
def catchLog(tag : String, fun : ()=>Unit) {
|
||||
Log.d(TAG, "catchLog(" + tag + ")")
|
||||
try {
|
||||
fun()
|
||||
} catch {
|
||||
case e : Exception => e.printStackTrace(); Log.d(TAG, tag + " execption: " + e)
|
||||
}
|
||||
}
|
||||
|
||||
def shutdown() {
|
||||
Log.d(TAG, "shutdown()")
|
||||
this.synchronized {
|
||||
running = false
|
||||
catchLog("socket.close", socket.close)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Kiss {
|
||||
// escape sequences
|
||||
val FEND = 0xC0
|
||||
val FESC = 0xDB
|
||||
val TFEND = 0xDC
|
||||
val TFESC = 0xDD
|
||||
|
||||
// commands
|
||||
val CMD_DATA = 0x00
|
||||
}
|
||||
|
||||
class KissReader(is : InputStream) {
|
||||
def readPacket() : String = {
|
||||
import Kiss._
|
||||
val buf = scala.collection.mutable.ListBuffer[Byte]()
|
||||
do {
|
||||
var ch = is.read()
|
||||
Log.d(TAG, "KissReader.readPacket: %02X '%c'".format(ch, ch))
|
||||
ch match {
|
||||
case FEND =>
|
||||
if (buf.length > 0) {
|
||||
Log.d(TAG, "KissReader.readPacket: sending back %s".format(new String(buf.toArray)))
|
||||
try {
|
||||
return Parser.parseAX25(buf.toArray).toString().trim()
|
||||
} catch {
|
||||
case e => buf.clear()
|
||||
}
|
||||
}
|
||||
case FESC => is.read() match {
|
||||
case TFEND => buf.append(FEND.toByte)
|
||||
case TFESC => buf.append(FESC.toByte)
|
||||
case _ =>
|
||||
}
|
||||
case -1 => throw new java.io.IOException("KissReader out of data")
|
||||
case 0 =>
|
||||
// hack: ignore 0x00 byte at start of frame, this is the command
|
||||
if (buf.length != 0)
|
||||
buf.append(ch.toByte)
|
||||
else
|
||||
Log.d(TAG, "KissReader.readPacket: ignoring command byte")
|
||||
case _ =>
|
||||
buf.append(ch.toByte)
|
||||
}
|
||||
} while (true)
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
class KissWriter(os : OutputStream) {
|
||||
def writePacket(p : Array[Byte]) {
|
||||
Log.d(TAG, "KissWriter.writePacket: %s".format(p))
|
||||
os.write(Kiss.FEND)
|
||||
os.write(Kiss.CMD_DATA)
|
||||
os.write(p)
|
||||
os.write(Kiss.FEND)
|
||||
os.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ładowanie…
Reference in New Issue