gracefully handle when an esp32 bluetooth link slowly browns out

This commit is contained in:
geeksville
2020-02-17 18:46:20 -08:00
parent b3026ba6be
commit bdd6e5de6c
6 changed files with 104 additions and 89 deletions

View File

@@ -259,7 +259,7 @@ class MainActivity : AppCompatActivity(), Logging,
filter.addAction(MeshService.ACTION_NODE_CHANGE)
filter.addAction(MeshService.ACTION_RECEIVED_DATA)
registerReceiver(meshServiceReceiver, filter)
receiverRegistered = true;
}
private fun unregisterMeshReceiver() {

View File

@@ -1,7 +1,10 @@
package com.geeksville.mesh.model
import android.os.RemoteException
import androidx.compose.mutableStateOf
import com.geeksville.android.Logging
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.utf8
import java.util.*
/**
@@ -35,9 +38,36 @@ object MessagesState : Logging {
a.size == b.size // If the # of messages changes, consider it important for rerender
})
/// add a message our GUI list of past msgs
fun addMessage(m: TextMessage) {
val l = messages.value.toMutableList()
l.add(m)
messages.value = l
}
/// Send a message and added it to our GUI log
fun sendMessage(str: String, dest: String? = null) {
var error: String? = null
val service = UIState.meshService
if (service != null)
try {
service.sendData(
dest,
str.toByteArray(utf8),
MeshProtos.Data.Type.CLEAR_TEXT_VALUE
)
} catch (ex: RemoteException) {
error = "Error: ${ex.message}"
}
else
error = "Error: No Mesh service"
MessagesState.addMessage(
TextMessage(
NodeDB.myId.value,
str,
errorMessage = error
)
)
}
}

View File

@@ -9,6 +9,7 @@ import android.content.*
import android.graphics.Color
import android.os.Build
import android.os.IBinder
import android.os.RemoteException
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.PRIORITY_MIN
@@ -538,58 +539,64 @@ class MeshService : Service(), Logging {
isConnected = c
if (c) {
// Do our startup init
try {
// 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
// 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()
)
val myInfo = MeshProtos.MyNodeInfo.parseFrom(
connectedRadio.readMyNode()
)
val mynodeinfo = MyNodeInfo(myInfo.myNodeNum, myInfo.hasGps)
myNodeInfo = mynodeinfo
// Ask for the current node DB
connectedRadio.restartNodeInfo()
val mynodeinfo = MyNodeInfo(myInfo.myNodeNum, myInfo.hasGps)
myNodeInfo = mynodeinfo
// 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")
// Ask for the current node DB
connectedRadio.restartNodeInfo()
// Just replace/add any entry
updateNodeInfo(info.num) {
if (info.hasUser())
it.user =
MeshUser(
info.user.id,
info.user.longName,
info.user.shortName
)
// 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
)
if (info.hasPosition())
it.position = Position(
info.position.latitude,
info.position.longitude,
info.position.altitude
)
it.lastSeen = info.lastSeen
}
it.lastSeen = info.lastSeen
// advance to next
infoBytes = connectedRadio.readNodeInfo()
}
// advance to next
infoBytes = connectedRadio.readNodeInfo()
// we don't ask for GPS locations from android if our device has a built in GPS
if (!mynodeinfo.hasGPS)
startLocationRequests()
else
debug("Our radio has a built in GPS, so not reading GPS in phone")
} catch (ex: RemoteException) {
// It seems that when the ESP32 goes offline it can briefly come back for a 100ms ish which
// causes the phone to try and reconnect. If we fail downloading our initial radio state we don't want to
// claim we have a valid connection still
isConnected = false;
throw ex; // Important to rethrow so that we don't tell the app all is well
}
// we don't ask for GPS locations from android if our device has a built in GPS
if (!mynodeinfo.hasGPS)
startLocationRequests()
else
debug("Our radio has a built in GPS, so not reading GPS in phone")
} else {
// lost radio connection, therefore no need to keep listening to GPS
stopLocationRequests()
@@ -703,8 +710,7 @@ class MeshService : Service(), Logging {
// encapsulate our payload in the proper protobufs and fire it off
val packet = buildMeshPacket(destId) {
data = MeshProtos.Data.newBuilder().also {
it.typ =
MeshProtos.Data.Type.SIGNAL_OPAQUE
it.typ = MeshProtos.Data.Type.forNumber(typ)
it.payload = ByteString.copyFrom(payloadIn)
}.build()
}

View File

@@ -265,22 +265,25 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
* Called from our big GATT callback, completes the current job and then schedules a new one
*/
private fun <T : Any> completeWork(status: Int, res: T) {
exceptionReporter {
// We might unexpectedly fail inside here, but we don't want to pass that exception back up to the bluetooth GATT layer
// startup next job in queue before calling the completion handler
val work =
synchronized(workQueue) {
val w = currentWork!! // will throw if null, which is helpful
currentWork = null // We are now no longer working on anything
// startup next job in queue before calling the completion handler
val work =
synchronized(workQueue) {
val w = currentWork!! // will throw if null, which is helpful
currentWork = null // We are now no longer working on anything
startNewWork()
w
}
startNewWork()
w
}
debug("work ${work.tag} is completed, resuming status=$status, res=$res")
if (status != 0)
work.completion.resumeWithException(IOException("Bluetooth status=$status"))
else
work.completion.resume(Result.success(res) as Result<Nothing>)
debug("work ${work.tag} is completed, resuming status=$status, res=$res")
if (status != 0)
work.completion.resumeWithException(IOException("Bluetooth status=$status while doing ${work.tag}"))
else
work.completion.resume(Result.success(res) as Result<Nothing>)
}
}
/**

View File

@@ -1,6 +1,5 @@
package com.geeksville.mesh.ui
import android.os.RemoteException
import androidx.compose.Composable
import androidx.compose.state
import androidx.ui.core.Modifier
@@ -21,20 +20,17 @@ import androidx.ui.material.surface.Surface
import androidx.ui.text.TextStyle
import androidx.ui.tooling.preview.Preview
import androidx.ui.unit.dp
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.model.MessagesState
import com.geeksville.mesh.model.MessagesState.messages
import com.geeksville.mesh.model.NodeDB
import com.geeksville.mesh.model.TextMessage
import com.geeksville.mesh.model.UIState
import com.geeksville.mesh.utf8
import java.text.SimpleDateFormat
private val dateFormat = SimpleDateFormat("h:mm a")
val TimestampEmphasis = object : Emphasis {
override fun emphasize(color: Color) = color.copy(alpha = 0.12f)
override fun emphasize(color: Color) = color.copy(alpha = 0.25f)
}
@@ -115,29 +111,8 @@ fun MessagesContent() {
MessagesState.info("did IME action")
val str = message.value
var error: String? = null
val service = UIState.meshService
if (service != null)
try {
service.sendData(
null,
str.toByteArray(utf8),
MeshProtos.Data.Type.CLEAR_TEXT_VALUE
)
} catch (ex: RemoteException) {
error = "Error: ${ex.message}"
}
else
error = "Error: No Mesh service"
MessagesState.addMessage(
TextMessage(
NodeDB.myId.value,
str,
errorMessage = error
)
)
MessagesState.sendMessage(str)
message.value = "" // blow away the string the user just entered
},
modifier = LayoutPadding(4.dp)
)