mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-03-22 15:52:01 -04:00
clean up exception handling
This commit is contained in:
3
TODO.md
3
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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
10
app/src/main/java/com/geeksville/mesh/Constants.kt
Normal file
10
app/src/main/java/com/geeksville/mesh/Constants.kt
Normal 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"
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package com.geeksville.mesh
|
||||
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
|
||||
const val prefix = "com.geeksville.mesh"
|
||||
|
||||
class MeshUtilApplication : GeeksvilleApplication(null, "58e72ccc361883ea502510baa46580e3") {
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user