From 8421c53a6940a1c332d34523ff69e668eebedfe4 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 25 Jan 2020 10:00:57 -0800 Subject: [PATCH] clean up exception handling --- TODO.md | 3 +- app/src/main/AndroidManifest.xml | 4 + .../com/geeksville/mesh/IMeshService.aidl | 8 +- .../java/com/geeksville/mesh/Constants.kt | 10 ++ .../java/com/geeksville/mesh/MainActivity.kt | 26 ++++- .../java/com/geeksville/mesh/MeshService.kt | 104 ++++++++++++------ .../geeksville/mesh/MeshUtilApplication.kt | 1 - .../geeksville/mesh/RadioInterfaceService.kt | 14 +-- app/src/main/proto/mesh.proto | 12 +- 9 files changed, 124 insertions(+), 58 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/Constants.kt diff --git a/TODO.md b/TODO.md index 684e72d80..43d3ac85b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,5 @@ - +* test mesh service from activity * use android service from Signal * DONE handle failures in onCharWrite, instead of logAssert - because they can happen if device goes away * make test implementation of android service (doesn't use bluetooth) @@ -11,6 +11,7 @@ * connect to bluetooth device automatically using minimum power * have signal declare receivers: https://developer.android.com/guide/components/broadcasts#manifest-declared-receivers * fix BT device scanning +* call crashlytics from exceptionReporter!!! currently not logging failures caught there protobuf notes protoc -I=. --java_out /tmp mesh.proto diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 978e0c377..515aa6c2a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,6 +44,10 @@ android:name="com.mixpanel.android.MPConfig.DisableViewCrawler" android:value="true" /> + + NodeInfo(n) } /// Map a userid to a node/ node num, or throw an exception if not found - private fun toNodeInfo(id: String) = nodeDBbyID.getValue(id) + private fun toNodeInfo(id: String) = nodeDBbyID[id] ?: throw IdNotFoundException(id) private fun toNodeNum(id: String) = toNodeInfo(id).num @@ -141,6 +150,18 @@ class MeshService : Service(), Logging { /// Generate a new mesh packet builder with our node as the sender, and the specified recipient private fun newMeshPacketTo(id: String) = newMeshPacketTo(toNodeNum(id)) + // Helper to make it easy to build a subpacket in the proper protobufs + private fun buildMeshPacket( + destId: String, + initFn: MeshProtos.SubPacket.Builder.() -> Unit + ): MeshPacket = newMeshPacketTo(destId).apply { + payload = MeshProtos.MeshPayload.newBuilder().apply { + addSubPackets(MeshProtos.SubPacket.newBuilder().also { + initFn(it) + }.build()) + }.build() + }.build() + /// Update our model and resend as needed for a MeshPacket we just received from the radio private fun handleReceivedData(fromNum: Int, data: MeshProtos.Data) { val bytes = data.payload.toByteArray() @@ -173,6 +194,16 @@ class MeshService : Service(), Logging { } } + /// Update our DB of users based on someone sending out a User subpacket + 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 + } + } + /// Update our model and resend as needed for a MeshPacket we just received from the radio private fun handleReceivedMeshPacket(packet: MeshPacket) { val fromNum = packet.from @@ -200,12 +231,7 @@ class MeshService : Service(), Logging { handleReceivedData(fromNum, p.data) MeshProtos.SubPacket.USER_FIELD_NUMBER -> - updateNodeInfo(fromNum) { - it.user = MeshUser(p.user.id, p.user.longName, p.user.shortName) - - // This might have been the first time we know an ID for this node, so also update the by ID map - nodeDBbyID.set(p.user.id, it) - } + handleReceivedUser(fromNum, p.user) MeshProtos.SubPacket.WANT_NODE_FIELD_NUMBER -> { // This is managed by the radio on its own debug("Ignoring WANT_NODE from $fromNum") @@ -241,41 +267,57 @@ class MeshService : Service(), Logging { } private val binder = object : IMeshService.Stub() { - override fun setOwner(myId: String, longName: String, shortName: String) { - error("TODO setOwner $myId : $longName : $shortName") - } + // Note: bound methods don't get properly exception caught/logged, so do that with a wrapper + // per https://blog.classycode.com/dealing-with-exceptions-in-aidl-9ba904c6d63 - override fun sendOpaque(destId: String, payloadIn: ByteArray, typ: Int) { - info("sendOpaque $destId <- ${payloadIn.size}") + override fun setOwner(myId: String, longName: String, shortName: String) = + exceptionsToStrings { + error("TODO setOwner $myId : $longName : $shortName") - // encapsulate our payload in the proper protobufs and fire it off - val packet = newMeshPacketTo(destId).apply { - payload = MeshProtos.MeshPayload.newBuilder().apply { - addSubPackets(MeshProtos.SubPacket.newBuilder().apply { - data = MeshProtos.Data.newBuilder().also { - it.typ = MeshProtos.Data.Type.SIGNAL_OPAQUE - it.payload = ByteString.copyFrom(payloadIn) - }.build() - }.build()) + val user = MeshProtos.User.newBuilder().also { + it.id = myId + it.longName = longName + it.shortName = shortName }.build() - }.build() - sendToRadio(ToRadio.newBuilder().apply { - this.packet = packet - }) - } + // Also update our own map for our nodenum, by handling the packet just like packets from other users + if (ourNodeNum != -1) { + handleReceivedUser(ourNodeNum, user) + } - override fun getOnline(): Array { + sendToRadio(ToRadio.newBuilder().apply { + this.setOwner = user + }) + } + + override fun sendData(destId: String, payloadIn: ByteArray, typ: Int) = + exceptionsToStrings { + info("sendData $destId <- ${payloadIn.size} bytes") + + // 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.payload = ByteString.copyFrom(payloadIn) + }.build() + } + + sendToRadio(ToRadio.newBuilder().apply { + this.packet = packet + }) + } + + override fun getOnline(): Array = exceptionReporter { val r = nodeDBbyID.keys.toTypedArray() info("in getOnline, count=${r.size}") // return arrayOf("+16508675309") - return r + r } - override fun isConnected(): Boolean { + override fun isConnected(): Boolean = exceptionReporter { val r = this@MeshService.isConnected info("in isConnected=r") - return r + r } } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt b/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt index 5f418d76d..36fff96a5 100644 --- a/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt +++ b/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt @@ -2,7 +2,6 @@ package com.geeksville.mesh import com.geeksville.android.GeeksvilleApplication -const val prefix = "com.geeksville.mesh" class MeshUtilApplication : GeeksvilleApplication(null, "58e72ccc361883ea502510baa46580e3") { } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt index 9f97fbfda..37a881c60 100644 --- a/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt @@ -7,13 +7,6 @@ import com.geeksville.android.DebugLogFile import com.geeksville.android.Logging import com.google.protobuf.util.JsonFormat -const val EXTRA_CONNECTED = "$prefix.Connected" -const val EXTRA_PAYLOAD = "$prefix.Payload" -const val EXTRA_SENDER = "$prefix.Sender" -const val EXTRA_ID = "$prefix.Id" -const val EXTRA_ONLINE = "$prefix.Online" -const val EXTRA_TYP = "$prefix.Typ" - /** * Handles the bluetooth link with a mesh radio device. Does not cache any device state, * just does bluetooth comms etc... @@ -42,8 +35,7 @@ class RadioInterfaceService : JobIntentService(), Logging { * Payload will be the raw bytes which were contained within a MeshProtos.FromRadio protobuf */ const val RECEIVE_FROMRADIO_ACTION = "$prefix.RECEIVE_FROMRADIO" - - + /** * Convenience method for enqueuing work in to this service. */ @@ -80,7 +72,7 @@ class RadioInterfaceService : JobIntentService(), Logging { } /// Send a packet/command out the radio link - private fun sendToRadio(p: ByteArray) { + private fun handleSendToRadio(p: ByteArray) { // For debugging/logging purposes ONLY we convert back into a protobuf for readability val proto = MeshProtos.ToRadio.parseFrom(p) @@ -109,7 +101,7 @@ class RadioInterfaceService : JobIntentService(), Logging { // holding a wake lock for us at this point, so we can just go. debug("Executing work: $intent") when (intent.action) { - SEND_TORADIO_ACTION -> sendToRadio(intent.getByteArrayExtra(EXTRA_PAYLOAD)!!) + SEND_TORADIO_ACTION -> handleSendToRadio(intent.getByteArrayExtra(EXTRA_PAYLOAD)!!) else -> TODO("Unhandled case") } } diff --git a/app/src/main/proto/mesh.proto b/app/src/main/proto/mesh.proto index 8a08184c1..fa259cf38 100644 --- a/app/src/main/proto/mesh.proto +++ b/app/src/main/proto/mesh.proto @@ -11,7 +11,7 @@ MESH RADIO PROTOCOL Old TODO notes on the mesh radio protocol, merge into real docs below... -for each named group we have a preshared key known by all group members and wrapped around the device. +for each named group we have a pre-shared key known by all group members and wrapped around the device. you can only be in one group at a time (FIXME?!) To join the group we read a qr code with the preshared key and ParamsCodeEnum. that gets sent via bluetooth to the device. ParamsCodeEnum maps to a set of various radio params (regulatory region, @@ -63,11 +63,11 @@ message Data { /// eventually in some circumstances even signal might send messages in this form (see below) CLEAR_TEXT = 1; - /// a message receive acknowledgement, sent in cleartext - allows radio to show user that a message has been read by the recpient, optional + /// a message receive acknowledgement, sent in cleartext - allows radio to show user that a message has been read by the recipient, optional CLEAR_READACK = 2; /// Not yet used but eventually: - /// SIGNAL_CLEAR_OPAQUE = 3; // Unencrypted at the signal level, relying on the radio crypt only (to keep size small), but contains Signal control data radios don't care about (or non ascii text) + /// SIGNAL_CLEAR_DATA = 3; // Unencrypted at the signal level, relying on the radio crypt only (to keep size small), but contains Signal control data radios don't care about (ie non text) } Type typ = 1; // required @@ -85,7 +85,7 @@ message User { // Broadcast when a newly powered mesh node wants to find a node num it can use (see document for more // details) message WantNodeNum { - // No payload, just its existence is sufficent (desired node num will be in the from field) + // No payload, just its existence is sufficient (desired node num will be in the from field) } // Sent to a node which has requested a nodenum when it is told it can't have it @@ -118,7 +118,7 @@ message MeshPacket { MeshPayload payload = 3; } -// Full settings (center freq, spread factor, preshared secret key etc...) needed to configure a radio +// Full settings (center freq, spread factor, pre-shared secret key etc...) needed to configure a radio message RadioConfig { // FIXME } @@ -129,7 +129,7 @@ The bluetooth to device link: Old BTLE protocol docs from TODO, merge in above and make real docs... -use protocol buffers, and nanopb +use protocol buffers, and NanoPB messages from device to phone: POSITION_UPDATE (..., time)