From 165df2c4dec9450e800915ba0b90b688f89b425f Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 17 Feb 2020 15:39:49 -0800 Subject: [PATCH] allow sending broadcasts and cope with missing mesh services --- .../com/geeksville/mesh/IMeshService.aidl | 2 ++ .../java/com/geeksville/mesh/MainActivity.kt | 20 ++++++------- .../main/java/com/geeksville/mesh/NodeInfo.kt | 13 ++++---- .../geeksville/mesh/model/MessagesState.kt | 9 +++++- .../java/com/geeksville/mesh/model/UIState.kt | 2 ++ .../geeksville/mesh/service/MeshService.kt | 21 +++++++++---- .../java/com/geeksville/mesh/ui/Messages.kt | 30 +++++++++++++++---- .../java/com/geeksville/mesh/ui/UserIcon.kt | 6 +++- 8 files changed, 73 insertions(+), 30 deletions(-) diff --git a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl index dff283be7..ed2bca81c 100644 --- a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl +++ b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl @@ -28,6 +28,8 @@ interface IMeshService { Send an opaque packet to a specified node name typ is defined in mesh.proto Data.Type. For now juse use 0 to mean opaque bytes. + + destId can be null to indicate "broadcast message" */ void sendData(String destId, in byte[] payload, int typ); diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 0f7a67acc..71d980946 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -80,6 +80,9 @@ eventually: make a custom theme: https://github.com/material-components/material-components-android/tree/master/material-theme-builder */ +val utf8 = Charset.forName("UTF-8") + + class MainActivity : AppCompatActivity(), Logging, ActivityCompat.OnRequestPermissionsResultCallback { @@ -90,8 +93,6 @@ class MainActivity : AppCompatActivity(), Logging, } - private val utf8 = Charset.forName("UTF-8") - private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) { val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothManager.adapter @@ -154,12 +155,12 @@ class MainActivity : AppCompatActivity(), Logging, private fun setOwner() { // Note: we are careful to not set a new unique ID val name = UIState.ownerName.value - meshService!!.setOwner(null, name, getInitials(name)) + UIState.meshService!!.setOwner(null, name, getInitials(name)) } private fun sendTestPackets() { exceptionReporter { - val m = meshService!! + val m = UIState.meshService!! // Do some test operations val testPayload = "hello world".toByteArray() @@ -270,7 +271,7 @@ class MainActivity : AppCompatActivity(), Logging, /// Read the config bytes from the radio so we can show them in our GUI, the radio's copy is ground truth private fun readRadioConfig() { - val bytes = meshService!!.radioConfig + val bytes = UIState.meshService!!.radioConfig val config = MeshProtos.RadioConfig.parseFrom(bytes) UIState.radioConfig.value = config @@ -340,14 +341,13 @@ class MainActivity : AppCompatActivity(), Logging, } } - private var meshService: IMeshService? = null private var isBound = false private var serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) = exceptionReporter { val m = IMeshService.Stub.asInterface(service) - meshService = m + UIState.meshService = m // We don't start listening for packets until after we are connected to the service registerMeshReceiver() @@ -367,14 +367,14 @@ class MainActivity : AppCompatActivity(), Logging, override fun onServiceDisconnected(name: ComponentName) { warn("The mesh service has disconnected") unregisterMeshReceiver() - meshService = null + UIState.meshService = null } } private fun bindMeshService() { debug("Binding to mesh service!") // we bind using the well known name, to make sure 3rd party apps could also - logAssert(meshService == null) + logAssert(UIState.meshService == null) val intent = MeshService.startService(this) if (intent != null) { @@ -391,7 +391,7 @@ class MainActivity : AppCompatActivity(), Logging, debug("Unbinding from mesh service!") if (isBound) unbindService(serviceConnection) - meshService = null + UIState.meshService = null } override fun onPause() { diff --git a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt index e7e03b658..23c548fd8 100644 --- a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt @@ -89,21 +89,22 @@ data class NodeInfo( } /// @return distance in meters to some other node (or null if unknown) - fun distance(o: NodeInfo?): Double? { + fun distance(o: NodeInfo?): Int? { val p = position val op = o?.position return if (p != null && op != null) - p.distance(op) + p.distance(op).toInt() else null } /// @return a nice human readable string for the distance, or null for unknown fun distanceStr(o: NodeInfo?) = distance(o)?.let { dist -> - if (dist < 1000) - "%.0f m".format(dist) - else - "%.1f km".format(dist / 1000) + when { + dist == 0 -> null // same point + dist < 1000 -> "%.0f m".format(dist) + else -> "%.1f km".format(dist / 1000.0) + } } override fun writeToParcel(parcel: Parcel, flags: Int) { 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 83d521cec..8de517114 100644 --- a/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt @@ -6,8 +6,15 @@ import java.util.* /** * the model object for a text message + * + * if errorMessage is set then we had a problem sending this message */ -data class TextMessage(val from: String, val text: String, val date: Date = Date()) +data class TextMessage( + val from: String, + val text: String, + val date: Date = Date(), + val errorMessage: String? = null +) object MessagesState : Logging { diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 0a8812532..a28f327c2 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -2,6 +2,7 @@ package com.geeksville.mesh.model import android.util.Base64 import androidx.compose.mutableStateOf +import com.geeksville.mesh.IMeshService import com.geeksville.mesh.MeshProtos /// FIXME - figure out how to merge this staate with the AppStatus Model @@ -10,6 +11,7 @@ object UIState { /// Kinda ugly - created in the activity but used from Compose - figure out if there is a cleaner way GIXME // lateinit var googleSignInClient: GoogleSignInClient + var meshService: IMeshService? = null /// Are we connected to our radio device val isConnected = mutableStateOf(false) 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 908545a41..805bd1690 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -418,12 +418,21 @@ class MeshService : Service(), Logging { to = idNum } - /// Generate a new mesh packet builder with our node as the sender, and the specified recipient - private fun newMeshPacketTo(id: String) = newMeshPacketTo(toNodeNum(id)) + /** + * Generate a new mesh packet builder with our node as the sender, and the specified recipient + * + * If id is null we assume a broadcast message + */ + private fun newMeshPacketTo(id: String?) = + newMeshPacketTo(if (id != null) toNodeNum(id) else NODENUM_BROADCAST) - // Helper to make it easy to build a subpacket in the proper protobufs + /** + * Helper to make it easy to build a subpacket in the proper protobufs + * + * If destId is null we assume a broadcast message + */ private fun buildMeshPacket( - destId: String, + destId: String?, initFn: MeshProtos.SubPacket.Builder.() -> Unit ): MeshPacket = newMeshPacketTo(destId).apply { payload = MeshProtos.SubPacket.newBuilder().also { @@ -687,9 +696,9 @@ class MeshService : Service(), Logging { connectedRadio.writeOwner(user.toByteArray()) } - override fun sendData(destId: String, payloadIn: ByteArray, typ: Int) = + override fun sendData(destId: String?, payloadIn: ByteArray, typ: Int) = toRemoteExceptions { - info("sendData $destId <- ${payloadIn.size} bytes") + info("sendData dest=$destId <- ${payloadIn.size} bytes") // encapsulate our payload in the proper protobufs and fire it off val packet = buildMeshPacket(destId) { 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 b9f360a93..5336a69d8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Messages.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Messages.kt @@ -20,10 +20,13 @@ 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 @@ -58,11 +61,11 @@ fun MessageCard(msg: TextMessage, modifier: Modifier = Modifier.None) { style = MaterialTheme.typography().caption ) } - } - Text( - text = msg.text - ) + if (msg.errorMessage != null) + Text(text = msg.errorMessage, style = TextStyle(color = palette.error)) + else + Text(text = msg.text) } } } @@ -109,10 +112,25 @@ fun MessagesContent() { imeAction = ImeAction.Send, onImeActionPerformed = { MessagesState.info("did IME action") + + val str = message.value + + var error: String? = null + val service = UIState.meshService + if (service != null) + service.sendData( + null, + str.toByteArray(utf8), + MeshProtos.Data.Type.CLEAR_TEXT_VALUE + ) + else + error = "Error: No Mesh service" + MessagesState.addMessage( TextMessage( - "fixme", - message.value + NodeDB.myId.value, + str, + errorMessage = error ) ) }, diff --git a/app/src/main/java/com/geeksville/mesh/ui/UserIcon.kt b/app/src/main/java/com/geeksville/mesh/ui/UserIcon.kt index e40622ce3..0ef2dc151 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UserIcon.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UserIcon.kt @@ -5,8 +5,10 @@ import androidx.ui.core.Modifier import androidx.ui.core.Text import androidx.ui.layout.Column import androidx.ui.layout.LayoutGravity +import androidx.ui.layout.LayoutWidth import androidx.ui.material.MaterialTheme import androidx.ui.tooling.preview.Preview +import androidx.ui.unit.dp import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.R import com.geeksville.mesh.model.NodeDB @@ -14,10 +16,12 @@ import com.geeksville.mesh.model.NodeDB /** * Show the user icon for a particular user with distance from the operator and a small pointer * indicating their direction + * + * This component is fixed width to simplify layouts. */ @Composable fun UserIcon(user: NodeInfo? = null, modifier: Modifier = Modifier.None) { - Column(modifier = modifier) { + Column(modifier = modifier + LayoutWidth(60.dp)) { VectorImage( id = R.drawable.ic_twotone_person_24, tint = palette.onSecondary,