diff --git a/TODO.md b/TODO.md index b95419e7..fdcc4ac0 100644 --- a/TODO.md +++ b/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 \ No newline at end of file +* 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 \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 36cc6515..7af4d592 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -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() { diff --git a/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt b/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt index 8de51711..3452c7eb 100644 --- a/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt @@ -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 + ) + ) + } } diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index bb94f388..47785fae 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -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() } diff --git a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt index b49fca13..7819de45 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt @@ -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 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) + 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) + } } /** diff --git a/app/src/main/java/com/geeksville/mesh/ui/Messages.kt b/app/src/main/java/com/geeksville/mesh/ui/Messages.kt index 7109a7e4..f4fe33f6 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Messages.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Messages.kt @@ -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) )