kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
WIP rx text message GUI
rodzic
17f7a05326
commit
984bc460d6
|
@ -8,6 +8,8 @@ package com.geeksville.mesh;
|
|||
*/
|
||||
interface IMeshService {
|
||||
/// Tell the service where to send its broadcasts of received packets
|
||||
/// This call is only required for manifest declared receivers. If your receiver is context-registered
|
||||
/// you don't need this.
|
||||
void subscribeReceiver(String packageName, String receiverName);
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,11 +2,15 @@ package com.geeksville.mesh
|
|||
|
||||
const val prefix = "com.geeksville.mesh"
|
||||
|
||||
|
||||
//
|
||||
// standard EXTRA bundle definitions
|
||||
//
|
||||
|
||||
// a bool true means now connected, false means not
|
||||
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_NODEINFO = "$prefix.NodeInfo"
|
||||
const val EXTRA_TYP = "$prefix.Typ"
|
||||
|
|
|
@ -3,10 +3,7 @@ package com.geeksville.mesh
|
|||
import android.Manifest
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
@ -31,6 +28,8 @@ import androidx.ui.tooling.preview.Preview
|
|||
import com.geeksville.android.Logging
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity(), Logging {
|
||||
|
@ -173,16 +172,79 @@ class MainActivity : AppCompatActivity(), Logging {
|
|||
}
|
||||
|
||||
requestPermission()
|
||||
|
||||
val filter = IntentFilter()
|
||||
filter.addAction("")
|
||||
registerReceiver(meshServiceReceiver, filter)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterReceiver(meshServiceReceiver)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
/// A map from nodeid to to nodeinfo
|
||||
private val nodes = mutableMapOf<String, NodeInfo>()
|
||||
|
||||
data class TextMessage(val date: Date, val from: String, val text: String)
|
||||
|
||||
private val messages = mutableListOf<TextMessage>()
|
||||
|
||||
/// Are we connected to our radio device
|
||||
private var isConnected = false
|
||||
|
||||
private val utf8 = Charset.forName("UTF-8")
|
||||
|
||||
private val meshServiceReceiver = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) = exceptionReporter {
|
||||
debug("Received from mesh service $intent")
|
||||
|
||||
when (intent.action) {
|
||||
MeshService.ACTION_NODE_CHANGE -> {
|
||||
warn("TODO nodechange")
|
||||
val info: NodeInfo = intent.getParcelableExtra(EXTRA_NODEINFO)!!
|
||||
|
||||
// We only care about nodes that have user info
|
||||
info.user?.id?.let {
|
||||
nodes[it] = info
|
||||
}
|
||||
}
|
||||
MeshService.ACTION_RECEIVED_DATA -> {
|
||||
warn("TODO rxopaqe")
|
||||
val sender = intent.getStringExtra(EXTRA_SENDER)!!
|
||||
val payload = intent.getByteArrayExtra(EXTRA_PAYLOAD)!!
|
||||
val typ = intent.getIntExtra(EXTRA_TYP, -1)!!
|
||||
|
||||
when (typ) {
|
||||
MeshProtos.Data.Type.CLEAR_TEXT_VALUE -> {
|
||||
// FIXME - use the real time from the packet
|
||||
messages.add(TextMessage(Date(), sender, payload.toString(utf8)))
|
||||
}
|
||||
else -> TODO()
|
||||
}
|
||||
}
|
||||
RadioInterfaceService.CONNECTCHANGED_ACTION -> {
|
||||
isConnected = intent.getBooleanExtra(EXTRA_CONNECTED, false)
|
||||
debug("connchange $isConnected")
|
||||
}
|
||||
else -> TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var meshService: IMeshService? = null
|
||||
private var isBound = false
|
||||
|
||||
private var serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||
override fun onServiceConnected(name: ComponentName, service: IBinder) = exceptionReporter {
|
||||
val m = IMeshService.Stub.asInterface(service)
|
||||
meshService = m
|
||||
|
||||
// FIXME - do actions for when we connect to the service
|
||||
// FIXME - do actions for when we connect to the service
|
||||
debug("did connect")
|
||||
|
||||
// FIXME: this still can't work this early because the send to +6508675310
|
||||
// requires a DB lookup which isn't yet populated (until the sim test packets
|
||||
// from the radio arrive)
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.content.*
|
|||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat.PRIORITY_MIN
|
||||
|
@ -18,11 +19,29 @@ import com.geeksville.util.exceptionReporter
|
|||
import com.geeksville.util.toOneLineString
|
||||
import com.geeksville.util.toRemoteExceptions
|
||||
import com.google.protobuf.ByteString
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import java.nio.charset.Charset
|
||||
|
||||
|
||||
class RadioNotConnectedException() : Exception("Can't find radio")
|
||||
|
||||
// model objects that directly map to the corresponding protobufs
|
||||
@Parcelize
|
||||
data class MeshUser(val id: String, val longName: String, val shortName: String) :
|
||||
Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class Position(val latitude: Double, val longitude: Double, val altitude: Int) :
|
||||
Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class NodeInfo(
|
||||
val num: Int, // This is immutable, and used as a key
|
||||
var user: MeshUser? = null,
|
||||
var position: Position? = null,
|
||||
var lastSeen: Long? = null
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
* Handles all the communication with android apps. Also keeps an internal model
|
||||
* of the network state.
|
||||
|
@ -32,6 +51,11 @@ class RadioNotConnectedException() : Exception("Can't find radio")
|
|||
class MeshService : Service(), Logging {
|
||||
|
||||
companion object {
|
||||
|
||||
/// Intents broadcast by MeshService
|
||||
const val ACTION_RECEIVED_DATA = "$prefix.RECEIVED_DATA"
|
||||
const val ACTION_NODE_CHANGE = "$prefix.NODE_CHANGE"
|
||||
|
||||
class IdNotFoundException(id: String) : Exception("ID not found $id")
|
||||
class NodeNumNotFoundException(id: Int) : Exception("NodeNum not found $id")
|
||||
class NotInMeshException() : Exception("We are not yet in a mesh")
|
||||
|
@ -41,6 +65,8 @@ class MeshService : Service(), Logging {
|
|||
|
||||
/// If the radio hasn't yet joined a mesh (i.e. no nodenum assigned)
|
||||
private const val NODE_NUM_NO_MESH = -1
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// A mapping of receiver class name to package name - used for explicit broadcasts
|
||||
|
@ -56,6 +82,7 @@ class MeshService : Service(), Logging {
|
|||
*/
|
||||
|
||||
private fun explicitBroadcast(intent: Intent) {
|
||||
sendBroadcast(intent) // We also do a regular (not explicit broadcast) so any context-registered rceivers will work
|
||||
clientPackages.forEach {
|
||||
intent.setClassName(it.value, it.key)
|
||||
sendBroadcast(intent)
|
||||
|
@ -68,18 +95,18 @@ class MeshService : Service(), Logging {
|
|||
* Sender will be a user ID string
|
||||
* Type will be the Data.Type enum code for this payload
|
||||
*/
|
||||
private fun broadcastReceivedOpaque(senderId: String, payload: ByteArray, typ: Int) {
|
||||
val intent = Intent("$prefix.RECEIVED_OPAQUE")
|
||||
private fun broadcastReceivedData(senderId: String, payload: ByteArray, typ: Int) {
|
||||
val intent = Intent(ACTION_RECEIVED_DATA)
|
||||
intent.putExtra(EXTRA_SENDER, senderId)
|
||||
intent.putExtra(EXTRA_PAYLOAD, payload)
|
||||
intent.putExtra(EXTRA_TYP, typ)
|
||||
explicitBroadcast(intent)
|
||||
}
|
||||
|
||||
private fun broadcastNodeChange(nodeId: String, isOnline: Boolean) {
|
||||
val intent = Intent("$prefix.NODE_CHANGE")
|
||||
intent.putExtra(EXTRA_ID, nodeId)
|
||||
intent.putExtra(EXTRA_ONLINE, isOnline)
|
||||
private fun broadcastNodeChange(info: NodeInfo) {
|
||||
debug("Broadcasting node change $info")
|
||||
val intent = Intent(ACTION_NODE_CHANGE)
|
||||
intent.putExtra(EXTRA_NODEINFO, info)
|
||||
explicitBroadcast(intent)
|
||||
}
|
||||
|
||||
|
@ -134,7 +161,7 @@ class MeshService : Service(), Logging {
|
|||
|
||||
private fun startForeground() {
|
||||
|
||||
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
// val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val channelId =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel()
|
||||
|
@ -204,17 +231,6 @@ class MeshService : Service(), Logging {
|
|||
super.onDestroy()
|
||||
}
|
||||
|
||||
// model objects that directly map to the corresponding protobufs
|
||||
data class MeshUser(val id: String, val longName: String, val shortName: String)
|
||||
|
||||
data class Position(val latitude: Double, val longitude: Double, val altitude: Int)
|
||||
data class NodeInfo(
|
||||
val num: Int, // This is immutable, and used as a key
|
||||
var user: MeshUser? = null,
|
||||
var position: Position? = null,
|
||||
var lastSeen: Long? = null
|
||||
)
|
||||
|
||||
///
|
||||
/// BEGINNING OF MODEL - FIXME, move elsewhere
|
||||
///
|
||||
|
@ -266,6 +282,8 @@ class MeshService : Service(), Logging {
|
|||
val userId = info.user?.id
|
||||
if (userId != null)
|
||||
nodeDBbyID[userId] = info
|
||||
|
||||
broadcastNodeChange(info)
|
||||
}
|
||||
|
||||
/// Generate a new mesh packet builder with our node as the sender, and the specified node num
|
||||
|
@ -301,13 +319,24 @@ class MeshService : Service(), Logging {
|
|||
/// the sending node ID if possible, else just its number
|
||||
val fromString = fromId ?: fromId.toString()
|
||||
|
||||
fun forwardData() {
|
||||
if (fromId == null)
|
||||
warn("Ignoring data from $fromNum because we don't yet know its ID")
|
||||
else {
|
||||
debug("Received data from $fromId ${bytes.size}")
|
||||
broadcastReceivedData(fromId, bytes, data.typValue)
|
||||
}
|
||||
}
|
||||
|
||||
when (data.typValue) {
|
||||
MeshProtos.Data.Type.CLEAR_TEXT_VALUE ->
|
||||
warn(
|
||||
"TODO ignoring CLEAR_TEXT from $fromString: ${bytes.toString(
|
||||
MeshProtos.Data.Type.CLEAR_TEXT_VALUE -> {
|
||||
debug(
|
||||
"FIXME - don't long this: Received CLEAR_TEXT from $fromString: ${bytes.toString(
|
||||
Charset.forName("UTF-8")
|
||||
)}"
|
||||
)
|
||||
forwardData()
|
||||
}
|
||||
|
||||
MeshProtos.Data.Type.CLEAR_READACK_VALUE ->
|
||||
warn(
|
||||
|
@ -315,12 +344,8 @@ class MeshService : Service(), Logging {
|
|||
)
|
||||
|
||||
MeshProtos.Data.Type.SIGNAL_OPAQUE_VALUE ->
|
||||
if (fromId == null)
|
||||
error("Ignoring opaque from $fromNum because we don't yet know its ID")
|
||||
else {
|
||||
debug("Received opaque from $fromId ${bytes.size}")
|
||||
broadcastReceivedOpaque(fromId, bytes, data.typValue)
|
||||
}
|
||||
forwardData()
|
||||
|
||||
else -> TODO()
|
||||
}
|
||||
}
|
||||
|
@ -341,6 +366,14 @@ class MeshService : Service(), Logging {
|
|||
val toNum = packet.to
|
||||
|
||||
val p = packet.payload
|
||||
|
||||
// Update our last seen based on any valid timestamps
|
||||
if (packet.rxTime != 0L) {
|
||||
updateNodeInfo(fromNum) {
|
||||
it.lastSeen = packet.rxTime
|
||||
}
|
||||
}
|
||||
|
||||
when (p.variantCase.number) {
|
||||
MeshProtos.SubPacket.POSITION_FIELD_NUMBER ->
|
||||
updateNodeInfo(fromNum) {
|
||||
|
@ -350,10 +383,6 @@ class MeshService : Service(), Logging {
|
|||
p.position.altitude
|
||||
)
|
||||
}
|
||||
MeshProtos.SubPacket.TIME_FIELD_NUMBER ->
|
||||
updateNodeInfo(fromNum) {
|
||||
it.lastSeen = p.time
|
||||
}
|
||||
MeshProtos.SubPacket.DATA_FIELD_NUMBER ->
|
||||
handleReceivedData(fromNum, p.data)
|
||||
|
||||
|
@ -378,7 +407,7 @@ class MeshService : Service(), Logging {
|
|||
val myInfo = MeshProtos.MyNodeInfo.parseFrom(connectedRadio.readMyNode())
|
||||
ourNodeNum = myInfo.myNodeNum
|
||||
|
||||
// Ask for the current node DB
|
||||
// Ask for the current node DB
|
||||
connectedRadio.restartNodeInfo()
|
||||
|
||||
// read all the infos until we get back null
|
||||
|
@ -390,7 +419,8 @@ class MeshService : Service(), Logging {
|
|||
// Just replace/add any entry
|
||||
updateNodeInfo(info.num) {
|
||||
if (info.hasUser())
|
||||
it.user = MeshUser(info.user.id, info.user.longName, info.user.shortName)
|
||||
it.user =
|
||||
MeshUser(info.user.id, info.user.longName, info.user.shortName)
|
||||
|
||||
if (info.hasPosition())
|
||||
it.position = Position(
|
||||
|
@ -429,7 +459,9 @@ class MeshService : Service(), Logging {
|
|||
MeshProtos.FromRadio.parseFrom(intent.getByteArrayExtra(EXTRA_PAYLOAD)!!)
|
||||
info("Received from radio service: ${proto.toOneLineString()}")
|
||||
when (proto.variantCase.number) {
|
||||
MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket(proto.packet)
|
||||
MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket(
|
||||
proto.packet
|
||||
)
|
||||
|
||||
else -> TODO("Unexpected FromRadio variant")
|
||||
}
|
||||
|
|
|
@ -89,6 +89,10 @@ class RadioInterfaceService : Service(), Logging {
|
|||
* Payload will be the raw bytes which were contained within a MeshProtos.FromRadio protobuf
|
||||
*/
|
||||
const val RECEIVE_FROMRADIO_ACTION = "$prefix.RECEIVE_FROMRADIO"
|
||||
|
||||
/**
|
||||
* This is broadcast when connection state changed (it is also rebroadcast by the MeshService)
|
||||
*/
|
||||
const val CONNECTCHANGED_ACTION = "$prefix.CONNECT_CHANGED"
|
||||
|
||||
private val BTM_SERVICE_UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd")
|
||||
|
|
|
@ -116,9 +116,6 @@ message SubPacket {
|
|||
oneof variant {
|
||||
Position position = 1;
|
||||
|
||||
// Times are typically not sent over the mesh, but they will be added to any Packet (chain of SubPacket)
|
||||
// sent to the phone (so the phone can know exact time of reception)
|
||||
uint64 time = 2; // msecs since 1970
|
||||
Data data = 3;
|
||||
User user = 4;
|
||||
}
|
||||
|
@ -141,6 +138,11 @@ message MeshPacket {
|
|||
// See note above:
|
||||
// MeshPayload payloads = 4;
|
||||
SubPacket payload = 3;
|
||||
|
||||
/// The time this message was received by the esp32 (msecs since 1970). Note: this field is _never_ sent on the radio link itself (to save space)
|
||||
/// Times are typically not sent over the mesh, but they will be added to any Packet (chain of SubPacket)
|
||||
/// sent to the phone (so the phone can know exact time of reception)
|
||||
uint64 rx_time = 4;
|
||||
}
|
||||
|
||||
// Full settings (center freq, spread factor, pre-shared secret key etc...) needed to configure a radio
|
||||
|
|
Ładowanie…
Reference in New Issue