clean up exception handling

This commit is contained in:
geeksville
2020-01-25 10:00:57 -08:00
parent 96b762fba9
commit 8421c53a69
9 changed files with 124 additions and 58 deletions

View File

@@ -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

View File

@@ -44,6 +44,10 @@
android:name="com.mixpanel.android.MPConfig.DisableViewCrawler"
android:value="true" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
<!-- we need bind job service for oreo -->
<service
android:name="com.geeksville.mesh.SoftwareUpdateService"

View File

@@ -6,15 +6,19 @@ package com.geeksville.mesh;
interface IMeshService {
/**
* Set the ID info for this node
@return null for success, or an error message for failure
*/
void setOwner(String myId, String longName, String shortName);
String setOwner(String myId, String longName, String shortName);
/*
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.
@return null for success, or an error message for failure
*/
void sendOpaque(String destId, in byte[] payload, int typ);
String sendData(String destId, in byte[] payload, int typ);
/**
Get the IDs of everyone on the mesh. You should also subscribe for NODE_CHANGE broadcasts.

View File

@@ -0,0 +1,10 @@
package com.geeksville.mesh
const val prefix = "com.geeksville.mesh"
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"

View File

@@ -9,6 +9,7 @@ import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Debug
import android.os.IBinder
import android.view.Menu
import android.view.MenuItem
@@ -27,6 +28,7 @@ import androidx.ui.material.Button
import androidx.ui.material.MaterialTheme
import androidx.ui.tooling.preview.Preview
import com.geeksville.android.Logging
import com.google.firebase.crashlytics.FirebaseCrashlytics
class MainActivity : AppCompatActivity(), Logging {
@@ -124,6 +126,12 @@ class MainActivity : AppCompatActivity(), Logging {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// We default to off in the manifest, FIXME turn on only if user approves
// leave off when running in the debugger
if (false && !Debug.isDebuggerConnected())
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true)
setContent {
composeView(meshServiceState)
}
@@ -147,14 +155,20 @@ class MainActivity : AppCompatActivity(), Logging {
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
meshService = IMeshService.Stub.asInterface(service)
val m = IMeshService.Stub.asInterface(service)
meshService = m
// Do some test operations
m.setOwner("+16508675309", "Kevin Xter", "kx")
val testPayload = "hello world".toByteArray()
m.sendData("+16508675310", testPayload, MeshProtos.Data.Type.SIGNAL_OPAQUE_VALUE)
m.sendData("+16508675310", testPayload, MeshProtos.Data.Type.CLEAR_TEXT_VALUE)
// FIXME this doesn't work because the model has already been copied into compose land?
runOnUiThread {
// FIXME - this can be removed?
meshServiceState.connected = meshService!!.isConnected
meshServiceState.onlineIds = meshService!!.online
}
// runOnUiThread { // FIXME - this can be removed?
meshServiceState.connected = m.isConnected
meshServiceState.onlineIds = m.online
// }
}
override fun onServiceDisconnected(name: ComponentName) {

View File

@@ -9,6 +9,8 @@ import android.os.IBinder
import com.geeksville.android.Logging
import com.geeksville.mesh.MeshProtos.MeshPacket
import com.geeksville.mesh.MeshProtos.ToRadio
import com.geeksville.util.exceptionReporter
import com.geeksville.util.exceptionsToStrings
import com.google.protobuf.ByteString
import java.nio.charset.Charset
@@ -20,6 +22,11 @@ import java.nio.charset.Charset
*/
class MeshService : Service(), Logging {
companion object {
class IdNotFoundException(id: String) : Exception("ID not found $id")
class NodeNumNotFoundException(id: Int) : Exception("NodeNum not found $id")
}
/*
see com.geeksville.mesh broadcast intents
// RECEIVED_OPAQUE for data received from other nodes
@@ -61,6 +68,7 @@ class MeshService : Service(), Logging {
override fun onCreate() {
super.onCreate()
info("Creating mesh service")
val filter = IntentFilter(RadioInterfaceService.RECEIVE_FROMRADIO_ACTION)
registerReceiver(radioInterfaceReceiver, filter)
@@ -73,6 +81,7 @@ class MeshService : Service(), Logging {
}
override fun onDestroy() {
info("Destroying mesh service")
unregisterReceiver(radioInterfaceReceiver)
super.onDestroy()
}
@@ -112,7 +121,7 @@ class MeshService : Service(), Logging {
///
/// Map a nodenum to a node, or throw an exception if not found
private fun toNodeInfo(n: Int) = nodeDBbyNodeNum.getValue(n)
private fun toNodeInfo(n: Int) = nodeDBbyNodeNum[n] ?: throw NodeNumNotFoundException(n)
/// Map a nodenum to the nodeid string, or throw an exception if not present
private fun toNodeID(n: Int) = toNodeInfo(n).user?.id
@@ -122,7 +131,7 @@ class MeshService : Service(), Logging {
nodeDBbyNodeNum.getOrPut(n) { -> 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<String> {
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<String> = 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
}
}
}

View File

@@ -2,7 +2,6 @@ package com.geeksville.mesh
import com.geeksville.android.GeeksvilleApplication
const val prefix = "com.geeksville.mesh"
class MeshUtilApplication : GeeksvilleApplication(null, "58e72ccc361883ea502510baa46580e3") {
}

View File

@@ -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")
}
}

View File

@@ -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)