kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
gracefully handle when an esp32 bluetooth link slowly browns out
rodzic
b3026ba6be
commit
bdd6e5de6c
11
TODO.md
11
TODO.md
|
@ -1,10 +1,6 @@
|
|||
# High priority
|
||||
MVP features required for first public alpha
|
||||
|
||||
* show real ID of me when I sent texts
|
||||
* keep text entry box at bottom of screen
|
||||
* show user icons in chat
|
||||
* when I enter texts, send them to the device
|
||||
* let user set name and shortname
|
||||
* take video
|
||||
|
||||
|
@ -60,6 +56,8 @@ Do this "Signal app compatible" release relatively soon after the alpha release
|
|||
# Medium priority
|
||||
Things for the betaish period.
|
||||
|
||||
* show user icons in chat
|
||||
* keep past messages in db, one db per channel
|
||||
* spend some quality power consumption tuning with https://developer.android.com/studio/profile/energy-profiler and https://developer.android.com/topic/performance/power/battery-historian
|
||||
* only publish gps positions once every 5 mins while we are connected to our radio _and_ someone else is in the mesh
|
||||
* Do PRIORITY_BALANCED_POWER_ACCURACY for our gps updates when no one in the mesh is nearer than 200 meters
|
||||
|
@ -130,4 +128,7 @@ Don't leave device discoverable. Don't let unpaired users do things with device
|
|||
* warn user to bt pair
|
||||
* suppress logging output if running a release build (required for play store)
|
||||
* provide gps location for devices that don't have it
|
||||
* prompt user to turnon bluetooth and bind
|
||||
* prompt user to turnon bluetooth and bind
|
||||
* show real ID of me when I sent texts
|
||||
* keep text entry box at bottom of screen
|
||||
* when I enter texts, send them to the device
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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>)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
Ładowanie…
Reference in New Issue