sforkowany z mirror/meshtastic-android
change to sync api mostly complte
rodzic
cc28333ff7
commit
dc8cb8446c
1
TODO.md
1
TODO.md
|
@ -1,6 +1,7 @@
|
|||
# High priority
|
||||
|
||||
* fix startup race conditions in services, allow reads to block as needed
|
||||
* if radio disconnects, we need to requeue a new connect attempt in RadioService
|
||||
* when notified phone should download messages
|
||||
* have phone use our local node number as its node number (instead of hardwired)
|
||||
* investigate the Signal SMS message flow path, see if I could just make Mesh a third peer to signal & sms?
|
||||
|
|
|
@ -2,7 +2,9 @@ package com.geeksville.mesh
|
|||
|
||||
const val prefix = "com.geeksville.mesh"
|
||||
|
||||
// a bool true means now connected, false means not
|
||||
const val EXTRA_CONNECTED = "$prefix.Connected"
|
||||
|
||||
const val EXTRA_PAYLOAD = "$prefix.Payload"
|
||||
const val EXTRA_SENDER = "$prefix.Sender"
|
||||
const val EXTRA_ID = "$prefix.Id"
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.app.Service
|
|||
import android.content.*
|
||||
import android.os.IBinder
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.concurrent.DeferredExecution
|
||||
import com.geeksville.mesh.MeshProtos.MeshPacket
|
||||
import com.geeksville.mesh.MeshProtos.ToRadio
|
||||
import com.geeksville.util.toOneLineString
|
||||
|
@ -12,6 +11,7 @@ import com.geeksville.util.toRemoteExceptions
|
|||
import com.google.protobuf.ByteString
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class RadioNotConnectedException() : Exception("Can't find radio")
|
||||
|
||||
/**
|
||||
* Handles all the communication with android apps. Also keeps an internal model
|
||||
|
@ -25,7 +25,6 @@ class MeshService : Service(), Logging {
|
|||
class IdNotFoundException(id: String) : Exception("ID not found $id")
|
||||
class NodeNumNotFoundException(id: Int) : Exception("NodeNum not found $id")
|
||||
class NotInMeshException() : Exception("We are not yet in a mesh")
|
||||
class RadioNotConnectedException() : Exception("Can't find radio")
|
||||
|
||||
/// If we haven't yet received a node number from the radio
|
||||
private const val NODE_NUM_UNKNOWN = -2
|
||||
|
@ -74,18 +73,22 @@ class MeshService : Service(), Logging {
|
|||
explicitBroadcast(intent)
|
||||
}
|
||||
|
||||
private val toRadioDeferred = DeferredExecution()
|
||||
/// Safely access the radio service, if not connected an exception will be thrown
|
||||
private val connectedRadio: IRadioInterfaceService
|
||||
get() {
|
||||
val s = radioService
|
||||
if (s == null || !isConnected)
|
||||
throw RadioNotConnectedException()
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
/// Send a command/packet to our radio. But cope with the possiblity that we might start up
|
||||
/// before we are fully bound to the RadioInterfaceService
|
||||
private fun sendToRadio(p: ToRadio.Builder) {
|
||||
val b = p.build().toByteArray()
|
||||
|
||||
val s = radioService
|
||||
if (s != null)
|
||||
s.sendToRadio(b)
|
||||
else
|
||||
toRadioDeferred.add { radioService!!.sendToRadio(b) }
|
||||
connectedRadio.sendToRadio(b)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
|
@ -94,24 +97,8 @@ class MeshService : Service(), Logging {
|
|||
|
||||
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 FIXME
|
||||
/* sendToRadio(ToRadio.newBuilder().apply {
|
||||
wantNodes = ToRadio.WantNodes.newBuilder().build()
|
||||
}) */
|
||||
|
||||
// Now send any packets which had previously been queued for clients
|
||||
toRadioDeferred.run()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
|
@ -124,6 +111,10 @@ class MeshService : Service(), Logging {
|
|||
|
||||
info("Creating mesh service")
|
||||
|
||||
// we listen for messages from the radio receiver _before_ trying to create the service
|
||||
val filter = IntentFilter(RadioInterfaceService.RECEIVE_FROMRADIO_ACTION)
|
||||
registerReceiver(radioInterfaceReceiver, filter)
|
||||
|
||||
// We in turn need to use the radiointerface service
|
||||
val intent = Intent(this, RadioInterfaceService::class.java)
|
||||
// intent.action = IMeshService::class.java.name
|
||||
|
@ -132,12 +123,10 @@ class MeshService : Service(), Logging {
|
|||
// the rest of our init will happen once we are in radioConnection.onServiceConnected
|
||||
}
|
||||
|
||||
private var radioReceiverRegistered = false
|
||||
|
||||
override fun onDestroy() {
|
||||
info("Destroying mesh service")
|
||||
if (radioReceiverRegistered)
|
||||
unregisterReceiver(radioInterfaceReceiver)
|
||||
unregisterReceiver(radioInterfaceReceiver)
|
||||
unbindService(radioConnection)
|
||||
radioService = null
|
||||
|
||||
|
@ -201,6 +190,11 @@ class MeshService : Service(), Logging {
|
|||
private fun updateNodeInfo(nodeNum: Int, updatefn: (NodeInfo) -> Unit) {
|
||||
val info = getOrCreateNodeInfo(nodeNum)
|
||||
updatefn(info)
|
||||
|
||||
// This might have been the first time we know an ID for this node, so also update the by ID map
|
||||
val userId = info.user?.id
|
||||
if (userId != null)
|
||||
nodeDBbyID[userId] = info
|
||||
}
|
||||
|
||||
/// Generate a new mesh packet builder with our node as the sender, and the specified node num
|
||||
|
@ -264,9 +258,6 @@ class MeshService : Service(), Logging {
|
|||
private fun handleReceivedUser(fromNum: Int, p: MeshProtos.User) {
|
||||
updateNodeInfo(fromNum) {
|
||||
it.user = MeshUser(p.id, p.longName, p.shortName)
|
||||
|
||||
// This might have been the first time we know an ID for this node, so also update the by ID map
|
||||
nodeDBbyID[p.id] = it
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,8 +300,49 @@ class MeshService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleReceivedNodeInfo(info: MeshProtos.NodeInfo) {
|
||||
TODO()
|
||||
|
||||
/// Called when we gain/lose connection to our radio
|
||||
private fun onConnectionChanged(c: Boolean) {
|
||||
isConnected = c
|
||||
if (c) {
|
||||
// Do our startup init
|
||||
|
||||
// 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
|
||||
|
||||
val myInfo = MeshProtos.MyNodeInfo.parseFrom(connectedRadio.readMyNode())
|
||||
ourNodeNum = myInfo.myNodeNum
|
||||
|
||||
// Ask for the current node DB
|
||||
connectedRadio.restartNodeInfo()
|
||||
|
||||
// read all the infos until we get back null
|
||||
var infoBytes = connectedRadio.readNodeInfo()
|
||||
while (infoBytes != null) {
|
||||
val info = MeshProtos.NodeInfo.parseFrom(infoBytes)
|
||||
debug("Received initial nodeinfo $info")
|
||||
|
||||
// Just replace/add any entry
|
||||
updateNodeInfo(info.num) {
|
||||
if (info.hasUser())
|
||||
it.user = MeshUser(info.user.id, info.user.longName, info.user.shortName)
|
||||
|
||||
if (info.hasPosition())
|
||||
it.position = Position(
|
||||
info.position.latitude,
|
||||
info.position.longitude,
|
||||
info.position.altitude
|
||||
)
|
||||
|
||||
it.lastSeen = info.lastSeen
|
||||
}
|
||||
|
||||
// advance to next
|
||||
infoBytes = connectedRadio.readNodeInfo()
|
||||
}
|
||||
}
|
||||
TODO("FIXME - set our owner, get node infos, set our local nodenum, dont process received packets until we have the full node db")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -320,17 +352,25 @@ class MeshService : Service(), Logging {
|
|||
private val radioInterfaceReceiver = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val proto = MeshProtos.FromRadio.parseFrom(intent.getByteArrayExtra(EXTRA_PAYLOAD)!!)
|
||||
info("Received from radio service: ${proto.toOneLineString()}")
|
||||
when (proto.variantCase.number) {
|
||||
MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket(proto.packet)
|
||||
/*
|
||||
FIXME - handle node info and setting my protonum
|
||||
MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> handleReceivedNodeInfo(proto.nodeInfo)
|
||||
MeshProtos.FromRadio.MY_NODE_NUM_FIELD_NUMBER -> ourNodeNum = proto.myNodeNum
|
||||
|
||||
*/
|
||||
else -> TODO("Unexpected FromRadio variant")
|
||||
when (intent.action) {
|
||||
RadioInterfaceService.CONNECTCHANGED_ACTION -> {
|
||||
onConnectionChanged(intent.getBooleanExtra(EXTRA_CONNECTED, false))
|
||||
explicitBroadcast(intent) // forward the connection change message to anyone who is listening to us
|
||||
}
|
||||
|
||||
RadioInterfaceService.RECEIVE_FROMRADIO_ACTION -> {
|
||||
val proto =
|
||||
MeshProtos.FromRadio.parseFrom(intent.getByteArrayExtra(EXTRA_PAYLOAD)!!)
|
||||
info("Received from radio service: ${proto.toOneLineString()}")
|
||||
when (proto.variantCase.number) {
|
||||
MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket(proto.packet)
|
||||
|
||||
else -> TODO("Unexpected FromRadio variant")
|
||||
}
|
||||
}
|
||||
|
||||
else -> TODO("Unexpected radio interface broadcast")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -358,11 +398,8 @@ class MeshService : Service(), Logging {
|
|||
handleReceivedUser(ourNodeNum, user)
|
||||
}
|
||||
|
||||
/* FIXME - set my owner info
|
||||
sendToRadio(ToRadio.newBuilder().apply {
|
||||
this.setOwner = user
|
||||
})
|
||||
*/
|
||||
// set my owner info
|
||||
connectedRadio.writeOwner(user.toByteArray())
|
||||
}
|
||||
|
||||
override fun sendData(destId: String, payloadIn: ByteArray, typ: Int) =
|
||||
|
|
|
@ -11,6 +11,7 @@ import android.os.IBinder
|
|||
import com.geeksville.android.DebugLogFile
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.concurrent.DeferredExecution
|
||||
import com.geeksville.util.toRemoteExceptions
|
||||
import com.google.protobuf.util.JsonFormat
|
||||
import java.util.*
|
||||
|
||||
|
@ -89,6 +90,7 @@ class RadioInterfaceService : Service(), Logging {
|
|||
* Payload will be the raw bytes which were contained within a MeshProtos.FromRadio protobuf
|
||||
*/
|
||||
const val RECEIVE_FROMRADIO_ACTION = "$prefix.RECEIVE_FROMRADIO"
|
||||
const val CONNECTCHANGED_ACTION = "$prefix.CONNECT_CHANGED"
|
||||
|
||||
private val BTM_SERVICE_UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd")
|
||||
private val BTM_FROMRADIO_CHARACTER =
|
||||
|
@ -141,16 +143,14 @@ class RadioInterfaceService : Service(), Logging {
|
|||
// for debug logging only
|
||||
private val jsonPrinter = JsonFormat.printer()
|
||||
|
||||
// We have talked to our device and consumed all of the FromRadio packets it had initially
|
||||
// waiting for us
|
||||
private var initCompleted = false
|
||||
private var isConnected = false
|
||||
|
||||
/// Work that users of our service want done, which might get deferred until after
|
||||
/// we have completed our initial connection
|
||||
private val clientOperations = DeferredExecution()
|
||||
|
||||
fun broadcastConnectionChanged(isConnected: Boolean) {
|
||||
val intent = Intent("$prefix.CONNECTION_CHANGED")
|
||||
private fun broadcastConnectionChanged(isConnected: Boolean) {
|
||||
val intent = Intent(CONNECTCHANGED_ACTION)
|
||||
intent.putExtra(EXTRA_CONNECTED, isConnected)
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
|
@ -173,28 +173,24 @@ class RadioInterfaceService : Service(), Logging {
|
|||
|
||||
/// Attempt to read from the fromRadio mailbox, if data is found broadcast it to android apps
|
||||
private fun doReadFromRadio() {
|
||||
safe.asyncReadCharacteristic(fromRadio) {
|
||||
val b = it.getOrThrow().value
|
||||
if (!isConnected)
|
||||
warn("Abandoning fromradio read because we are not connected")
|
||||
else
|
||||
safe.asyncReadCharacteristic(fromRadio) {
|
||||
val b = it.getOrThrow().value
|
||||
|
||||
if (b.isNotEmpty()) {
|
||||
debug("Received ${b.size} bytes from radio")
|
||||
handleFromRadio(b)
|
||||
if (b.isNotEmpty()) {
|
||||
debug("Received ${b.size} bytes from radio")
|
||||
handleFromRadio(b)
|
||||
|
||||
// Queue up another read, until we run out of packets
|
||||
doReadFromRadio()
|
||||
} else {
|
||||
debug("Done reading from radio, fromradio is empty")
|
||||
initCompleted = true
|
||||
doClientOperations()
|
||||
// Queue up another read, until we run out of packets
|
||||
doReadFromRadio()
|
||||
} else {
|
||||
debug("Done reading from radio, fromradio is empty")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If we are inited send any client requests
|
||||
private fun doClientOperations() {
|
||||
if (initCompleted)
|
||||
clientOperations.run()
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
@ -235,6 +231,11 @@ class RadioInterfaceService : Service(), Logging {
|
|||
fromRadio = service.getCharacteristic(BTM_FROMRADIO_CHARACTER)
|
||||
fromNum = service.getCharacteristic(BTM_FROMNUM_CHARACTER)
|
||||
|
||||
// Now tell clients they can (finally use the api)
|
||||
broadcastConnectionChanged(true)
|
||||
isConnected = true
|
||||
|
||||
// Immediately broadcast any queued packets sitting on the device
|
||||
doReadFromRadio()
|
||||
}
|
||||
}
|
||||
|
@ -255,50 +256,60 @@ class RadioInterfaceService : Service(), Logging {
|
|||
}
|
||||
|
||||
/**
|
||||
* We allow writes to bluetooth characteristics to be queued up until our init is completed.
|
||||
* do a synchronous write operation
|
||||
*/
|
||||
private fun doAsyncWrite(uuid: UUID, a: ByteArray) {
|
||||
debug("queuing ${a.size} bytes to $uuid")
|
||||
private fun doWrite(uuid: UUID, a: ByteArray) = toRemoteExceptions {
|
||||
if (!isConnected)
|
||||
throw RadioNotConnectedException()
|
||||
else {
|
||||
debug("queuing ${a.size} bytes to $uuid")
|
||||
|
||||
clientOperations.add {
|
||||
// Note: we generate a new characteristic each time, because we are about to
|
||||
// change the data and we want the data stored in the closure
|
||||
val toRadio = service.getCharacteristic(uuid)
|
||||
toRadio.value = a
|
||||
|
||||
safe.asyncWriteCharacteristic(toRadio) {
|
||||
it.getOrThrow() // FIXME, handle the error better
|
||||
debug("ToRadio write of ${a.size} bytes completed")
|
||||
}
|
||||
safe.writeCharacteristic(toRadio)
|
||||
debug("write of ${a.size} bytes completed")
|
||||
}
|
||||
}
|
||||
|
||||
doClientOperations()
|
||||
/**
|
||||
* do a synchronous read operation
|
||||
*/
|
||||
private fun doRead(uuid: UUID): ByteArray? = toRemoteExceptions {
|
||||
if (!isConnected)
|
||||
throw RadioNotConnectedException()
|
||||
else {
|
||||
// Note: we generate a new characteristic each time, because we are about to
|
||||
// change the data and we want the data stored in the closure
|
||||
val toRadio = service.getCharacteristic(uuid)
|
||||
var a = safe.readCharacteristic(toRadio).value
|
||||
debug("Read of $uuid got ${a.size} bytes")
|
||||
|
||||
if (a.isEmpty()) // An empty bluetooth response is converted to a null response for our clients
|
||||
a = null
|
||||
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
private val binder = object : IRadioInterfaceService.Stub() {
|
||||
// A write of any size to nodeinfo means restart reading
|
||||
override fun restartNodeInfo() = doAsyncWrite(BTM_NODEINFO_CHARACTER, ByteArray(0))
|
||||
override fun restartNodeInfo() = doWrite(BTM_NODEINFO_CHARACTER, ByteArray(0))
|
||||
|
||||
override fun readMyNode(): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
override fun readMyNode() = doRead(BTM_MYNODE_CHARACTER)!!
|
||||
|
||||
override fun sendToRadio(a: ByteArray) = doAsyncWrite(BTM_TORADIO_CHARACTER, a)
|
||||
override fun sendToRadio(a: ByteArray) = doWrite(BTM_TORADIO_CHARACTER, a)
|
||||
|
||||
override fun readRadioConfig(): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
override fun readRadioConfig() = doRead(BTM_RADIO_CHARACTER)!!
|
||||
|
||||
override fun readOwner(): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
override fun readOwner() = doRead(BTM_OWNER_CHARACTER)!!
|
||||
|
||||
override fun writeOwner(owner: ByteArray) = doAsyncWrite(BTM_OWNER_CHARACTER, owner)
|
||||
override fun writeOwner(owner: ByteArray) = doWrite(BTM_OWNER_CHARACTER, owner)
|
||||
|
||||
override fun writeRadioConfig(config: ByteArray) = doAsyncWrite(BTM_RADIO_CHARACTER, config)
|
||||
override fun writeRadioConfig(config: ByteArray) = doWrite(BTM_RADIO_CHARACTER, config)
|
||||
|
||||
override fun readNodeInfo(): ByteArray {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
override fun readNodeInfo() = doRead(BTM_NODEINFO_CHARACTER)
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
Logging {
|
||||
|
||||
/// Timeout before we declare a bluetooth operation failed
|
||||
private val timeoutMsec = 30 * 1000L
|
||||
var timeoutMsec = 5 * 1000L
|
||||
|
||||
/// Users can access the GATT directly as needed
|
||||
lateinit var gatt: BluetoothGatt
|
||||
|
|
Ładowanie…
Reference in New Issue