diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0f63a8e1..9e227dee 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -68,7 +68,6 @@ diff --git a/app/src/main/aidl/com/geeksville/mesh/IRadioInterfaceService.aidl b/app/src/main/aidl/com/geeksville/mesh/IRadioInterfaceService.aidl new file mode 100644 index 00000000..626042c6 --- /dev/null +++ b/app/src/main/aidl/com/geeksville/mesh/IRadioInterfaceService.aidl @@ -0,0 +1,9 @@ +// IRadioInterfaceService.aidl +package com.geeksville.mesh; + +// Declare any non-default types here with import statements + +interface IRadioInterfaceService { + + void sendToRadio(in byte [] a); +} diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 93fc0990..24c9a898 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -151,12 +151,14 @@ class MainActivity : AppCompatActivity(), Logging { } var meshService: IMeshService? = null - var isBound = false private val serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { val m = IMeshService.Stub.asInterface(service) meshService = m + + // FIXME - don't do these operations until we are informed we have a connection, otherwise + // the radio interface service might not yet be connected to the mesh service // Do some test operations m.setOwner("+16508675309", "Kevin Xter", "kx") @@ -189,19 +191,16 @@ class MainActivity : AppCompatActivity(), Logging { //val intent = Intent(this, MeshService::class.java) //intent.action = IMeshService::class.java.name - isBound = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) - logAssert(isBound) + logAssert(bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)) } private fun unbindMeshService() { // If we have received the service, and hence registered with // it, then now is the time to unregister. // if we never connected, do nothing - if (isBound) { - debug("Unbinding from mesh service!") - unbindService(serviceConnection) - meshService = null - } + debug("Unbinding from mesh service!") + unbindService(serviceConnection) + meshService = null } override fun onPause() { diff --git a/app/src/main/java/com/geeksville/mesh/MeshService.kt b/app/src/main/java/com/geeksville/mesh/MeshService.kt index 727d66e5..98c8ee7d 100644 --- a/app/src/main/java/com/geeksville/mesh/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/MeshService.kt @@ -1,10 +1,7 @@ package com.geeksville.mesh import android.app.Service -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter +import android.content.* import android.os.IBinder import com.geeksville.android.Logging import com.geeksville.mesh.MeshProtos.MeshPacket @@ -39,6 +36,8 @@ class MeshService : Service(), Logging { /// A mapping of receiver class name to package name - used for explicit broadcasts private val clientPackages = mutableMapOf() + private var radioService: IRadioInterfaceService? = null + /* see com.geeksville.mesh broadcast intents // RECEIVED_OPAQUE for data received from other nodes @@ -76,34 +75,59 @@ class MeshService : Service(), Logging { /// Send a command/packet to our radio private fun sendToRadio(p: ToRadio.Builder) { - RadioInterfaceService.sendToRadio(this, p.build().toByteArray()) + radioService!!.sendToRadio(p.build().toByteArray()) } override fun onBind(intent: Intent?): IBinder? { return binder } + private val radioConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + val filter = IntentFilter(RadioInterfaceService.RECEIVE_FROMRADIO_ACTION) + registerReceiver(radioInterfaceReceiver, filter) + radioReceiverRegistered = true + + val m = IRadioInterfaceService.Stub.asInterface(service) + radioService = m + + // FIXME - don't do this until after we see that the radio is connected to the phone + val sim = SimRadio(this@MeshService) + sim.start() // Fake up our node id info and some past packets from other nodes + + // Ask for the current node DB + sendToRadio(ToRadio.newBuilder().apply { + wantNodes = ToRadio.WantNodes.newBuilder().build() + }) + } + + override fun onServiceDisconnected(name: ComponentName?) { + radioService = null + } + } + override fun onCreate() { super.onCreate() info("Creating mesh service") - val filter = IntentFilter(RadioInterfaceService.RECEIVE_FROMRADIO_ACTION) - registerReceiver(radioInterfaceReceiver, filter) - // FIXME - don't do this until after we see that the radio is connected to the phone - val sim = SimRadio(this) - sim.start() // Fake up our node id info and some past packets from other nodes - - // Ask for the current node DB - sendToRadio(ToRadio.newBuilder().apply { - wantNodes = ToRadio.WantNodes.newBuilder().build() - }) + // We in turn need to use the radiointerface service + val intent = Intent(this, RadioInterfaceService::class.java) + // intent.action = IMeshService::class.java.name + logAssert(bindService(intent, radioConnection, Context.BIND_AUTO_CREATE)) + // the rest of our init will happen once we are in radioConnection.onServiceConnected } + private var radioReceiverRegistered = false + override fun onDestroy() { info("Destroying mesh service") - unregisterReceiver(radioInterfaceReceiver) + if (radioReceiverRegistered) + unregisterReceiver(radioInterfaceReceiver) + unbindService(radioConnection) + radioService = null + super.onDestroy() } diff --git a/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt index e46d809a..1223ef66 100644 --- a/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt @@ -1,11 +1,13 @@ package com.geeksville.mesh +import android.app.Service import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothGattCharacteristic import android.bluetooth.BluetoothManager import android.content.Context import android.content.Intent -import androidx.core.app.JobIntentService +import android.os.IBinder import com.geeksville.android.DebugLogFile import com.geeksville.android.Logging import com.google.protobuf.util.JsonFormat @@ -66,20 +68,9 @@ A variable keepAllPackets, if set to true will suppress this behavior and instea * Note - this class intentionally dumb. It doesn't understand protobuf framing etc... * It is designed to be simple so it can be stubbed out with a simulated version as needed. */ -class RadioInterfaceService : JobIntentService(), Logging { +class RadioInterfaceService : Service(), Logging { companion object { - /** - * Unique job ID for this service. Must be the same for all work. - */ - private const val JOB_ID = 1001 - - /** - * The SEND_TORADIO - * Payload will be the raw bytes which were contained within a MeshProtos.ToRadio protobuf - */ - const val SEND_TORADIO_ACTION = "$prefix.SEND_TORADIO" - /** * The RECEIVED_FROMRADIO * Payload will be the raw bytes which were contained within a MeshProtos.FromRadio protobuf @@ -94,26 +85,6 @@ class RadioInterfaceService : JobIntentService(), Logging { private val BTM_FROMNUM_CHARACTER = UUID.fromString("ed9da18c-a800-4f66-a670-aa7547e34453") - /** - * Convenience method for enqueuing work in to this service. - */ - fun enqueueWork(context: Context, work: Intent) { - enqueueWork( - context, - RadioInterfaceService::class.java, JOB_ID, work - ) - } - - /// Helper function to send a packet to the radio - fun sendToRadio(context: Context, a: ByteArray) { - val i = Intent(SEND_TORADIO_ACTION) - i.putExtra(EXTRA_PAYLOAD, a) - enqueueWork(context, i) - } - - // for debug logging only - private val jsonPrinter = JsonFormat.printer() - /// This is public only so that SimRadio can bootstrap our message flow fun broadcastReceivedFromRadio(context: Context, payload: ByteArray) { val intent = Intent(RECEIVE_FROMRADIO_ACTION) @@ -131,8 +102,15 @@ class RadioInterfaceService : JobIntentService(), Logging { private lateinit var device: BluetoothDevice private lateinit var safe: SafeBluetooth + private lateinit var fromRadio: BluetoothGattCharacteristic + private lateinit var toRadio: BluetoothGattCharacteristic + private lateinit var fromNum: BluetoothGattCharacteristic + lateinit var sentPacketsLog: DebugLogFile // inited in onCreate + // for debug logging only + private val jsonPrinter = JsonFormat.printer() + fun broadcastConnectionChanged(isConnected: Boolean) { val intent = Intent("$prefix.CONNECTION_CHANGED") intent.putExtra(EXTRA_CONNECTED, isConnected) @@ -155,10 +133,16 @@ class RadioInterfaceService : JobIntentService(), Logging { broadcastReceivedFromRadio(this, p) } + /// Attempt to read from the fromRadio mailbox, if data is found broadcast it to android apps + private fun doReadFromRadio() { + safe.asyncReadCharacteristic(fromRadio) { + logAssert(it.isSuccess) // FIXME, handle failure + } + } + override fun onCreate() { super.onCreate() - // FIXME, the lifecycle is wrong for jobintentservice, change to a regular service // FIXME, let user GUI select which device we are talking to // Note: this call does no comms, it just creates the device object (even if the @@ -171,9 +155,24 @@ class RadioInterfaceService : JobIntentService(), Logging { // comes in range (even if we made this connect call long ago when we got powered on) // see https://stackoverflow.com/questions/40156699/which-correct-flag-of-autoconnect-in-connectgatt-of-ble for // more info - // FIXME, can't use sync connect here - because it could take a LONG time - // FIXME, don't use sync api at all - because our operations are so simple and atomic - safe.connect(true) + // FIXME, broadcast connection lost/gained via broadcastConnecionChanged + safe.asyncConnect(true) { connRes -> + // This callback is invoked after we are connected + + logAssert(connRes.isSuccess) // FIXME, instead just try to reconnect? + + safe.asyncDiscoverServices { discRes -> + logAssert(discRes.isSuccess) // IXME, instead just try to reconnect? + + val service = safe.gatt.services.find { it.uuid == BTM_SERVICE_UUID }!! + + fromRadio = service.getCharacteristic(BTM_FROMRADIO_CHARACTER) + toRadio = service.getCharacteristic(BTM_FROMRADIO_CHARACTER) + fromNum = service.getCharacteristic(BTM_FROMNUM_CHARACTER) + + doReadFromRadio() + } + } sentPacketsLog = DebugLogFile(this, "sent_log.json") } @@ -183,13 +182,13 @@ class RadioInterfaceService : JobIntentService(), Logging { super.onDestroy() } - override fun onHandleWork(intent: Intent) { // We have received work to do. The system or framework is already - // holding a wake lock for us at this point, so we can just go. - debug("Executing work: $intent") - when (intent.action) { - SEND_TORADIO_ACTION -> handleSendToRadio(intent.getByteArrayExtra(EXTRA_PAYLOAD)!!) - else -> TODO("Unhandled case") - } + override fun onBind(intent: Intent?): IBinder? { + return binder; } + private val binder = object : IRadioInterfaceService.Stub() { + override fun sendToRadio(a: ByteArray) { + handleFromRadio(a) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/SafeBluetooth.kt b/app/src/main/java/com/geeksville/mesh/SafeBluetooth.kt index a8c90a38..43d2f861 100644 --- a/app/src/main/java/com/geeksville/mesh/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/SafeBluetooth.kt @@ -133,7 +133,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD if (status != 0) work.completion.resumeWithException(IOException("Bluetooth status=$status")) else - work.completion.resume(Result.success(res) as Result) // FIXME, will this work? + work.completion.resume(Result.success(res) as Result) } /**