kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
clean up message API to/from mesh service, to allow clients to get
updates on message statuspull/40/head
rodzic
67d95039bf
commit
6031b5eaa0
|
@ -26,16 +26,16 @@ interface IMeshService {
|
|||
String getMyId();
|
||||
|
||||
/*
|
||||
Send an opaque packet to a specified node name
|
||||
Send a 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"
|
||||
|
||||
Returns true if the packet has been sent into the mesh, or false if it was merely queued
|
||||
inside the service - and will be delivered to mesh the next time we hear from our radio.
|
||||
messageStatus and id of the provided message will be updated by this routine to indicate
|
||||
message send status and the ID that can be used to locate the message in the future
|
||||
*/
|
||||
boolean sendData(String destId, in byte[] payload, int typ);
|
||||
void send(inout DataPacket packet);
|
||||
|
||||
/**
|
||||
Get the IDs of everyone on the mesh. You should also subscribe for NODE_CHANGE broadcasts.
|
||||
|
@ -46,7 +46,8 @@ interface IMeshService {
|
|||
/// It returns a RadioConfig protobuf.
|
||||
byte []getRadioConfig();
|
||||
|
||||
/// Return an list of MeshPacket protobuf (byte arrays) which were received while your client app was offline (recent messages only)
|
||||
/// Return an list of MeshPacket protobuf (byte arrays) which were received while your client app was offline (recent messages only).
|
||||
/// Also includes any messages we have sent recently (useful for finding current message status)
|
||||
List<DataPacket> getOldMessages();
|
||||
|
||||
/// This method is only intended for use in our GUI, so the user can set radio options
|
||||
|
@ -75,9 +76,9 @@ interface IMeshService {
|
|||
int getUpdateStatus();
|
||||
|
||||
// see com.geeksville.com.geeksville.mesh broadcast intents
|
||||
// RECEIVED_OPAQUE for data received from other nodes. payload will contain a DataPacket
|
||||
// RECEIVED_DATA for data received from other nodes. payload will contain a DataPacket
|
||||
// NODE_CHANGE for new IDs appearing or disappearing
|
||||
// CONNECTION_CHANGED for losing/gaining connection to the packet radio
|
||||
|
||||
// MESSAGE_STATUS_CHANGED for any message status changes (for sent messages only, other messages come via RECEIVED_DATA. payload will contain a message ID and a MessageStatus)
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.geeksville.mesh
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
@ -10,25 +11,37 @@ enum class MessageStatus : Parcelable {
|
|||
RECEIVED, // Came in from the mesh
|
||||
QUEUED, // Waiting to send to the mesh as soon as we connect to the device
|
||||
ENROUTE, // Delivered to the radio, but no ACK or NAK received
|
||||
DELIVERED // We received an ack
|
||||
DELIVERED, // We received an ack
|
||||
ERROR // We received back a nak, message not delivered
|
||||
}
|
||||
|
||||
/**
|
||||
* A parcelable version of the protobuf MeshPacket + Data subpacket.
|
||||
*/
|
||||
@Serializable
|
||||
@Parcelize
|
||||
data class DataPacket(
|
||||
val from: String, // a nodeID string
|
||||
val to: String, // a nodeID string
|
||||
val rxTime: Long, // msecs since 1970
|
||||
val id: Int,
|
||||
val dataType: Int,
|
||||
val bytes: ByteArray,
|
||||
val status: MessageStatus = MessageStatus.UNKNOWN
|
||||
var to: String? = ID_BROADCAST, // a nodeID string, or ID_BROADCAST for broadcast
|
||||
val bytes: ByteArray?,
|
||||
val dataType: Int, // A value such as MeshProtos.Data.Type.OPAQUE_VALUE
|
||||
var from: String? = ID_LOCAL, // a nodeID string, or ID_LOCAL for localhost
|
||||
var rxTime: Long = System.currentTimeMillis(), // msecs since 1970
|
||||
var id: Int = 0, // 0 means unassigned
|
||||
var status: MessageStatus? = MessageStatus.UNKNOWN
|
||||
) : Parcelable {
|
||||
|
||||
// Autogenerated comparision, because we have a byte array
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readString(),
|
||||
parcel.createByteArray(),
|
||||
parcel.readInt(),
|
||||
parcel.readString(),
|
||||
parcel.readLong(),
|
||||
parcel.readInt(),
|
||||
parcel.readParcelable(MessageStatus::class.java.classLoader)
|
||||
) {
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
@ -40,7 +53,8 @@ data class DataPacket(
|
|||
if (rxTime != other.rxTime) return false
|
||||
if (id != other.id) return false
|
||||
if (dataType != other.dataType) return false
|
||||
if (!bytes.contentEquals(other.bytes)) return false
|
||||
if (!bytes!!.contentEquals(other.bytes!!)) return false
|
||||
if (status != other.status) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -51,7 +65,52 @@ data class DataPacket(
|
|||
result = 31 * result + rxTime.hashCode()
|
||||
result = 31 * result + id
|
||||
result = 31 * result + dataType
|
||||
result = 31 * result + bytes.contentHashCode()
|
||||
result = 31 * result + bytes!!.contentHashCode()
|
||||
result = 31 * result + status.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(to)
|
||||
parcel.writeByteArray(bytes)
|
||||
parcel.writeInt(dataType)
|
||||
parcel.writeString(from)
|
||||
parcel.writeLong(rxTime)
|
||||
parcel.writeInt(id)
|
||||
parcel.writeParcelable(status, flags)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
/// Update our object from our parcel (used for inout parameters
|
||||
fun readFromParcel(parcel: Parcel) {
|
||||
to = parcel.readString()
|
||||
parcel.createByteArray()
|
||||
parcel.readInt()
|
||||
from = parcel.readString()
|
||||
rxTime = parcel.readLong()
|
||||
id = parcel.readInt()
|
||||
status = parcel.readParcelable(MessageStatus::class.java.classLoader)
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<DataPacket> {
|
||||
// Special node IDs that can be used for sending messages
|
||||
|
||||
/** the Node ID for broadcast destinations */
|
||||
const val ID_BROADCAST = "^all"
|
||||
|
||||
/** The Node ID for the local node - used for from when sender doesn't know our local node ID */
|
||||
const val ID_LOCAL = "^local"
|
||||
|
||||
override fun createFromParcel(parcel: Parcel): DataPacket {
|
||||
return DataPacket(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<DataPacket?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -297,15 +297,19 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
|
||||
// Do some test operations
|
||||
val testPayload = "hello world".toByteArray()
|
||||
m.sendData(
|
||||
"+16508675310",
|
||||
testPayload,
|
||||
MeshProtos.Data.Type.OPAQUE_VALUE
|
||||
m.send(
|
||||
DataPacket(
|
||||
"+16508675310",
|
||||
testPayload,
|
||||
MeshProtos.Data.Type.OPAQUE_VALUE
|
||||
)
|
||||
)
|
||||
m.sendData(
|
||||
"+16508675310",
|
||||
testPayload,
|
||||
MeshProtos.Data.Type.CLEAR_TEXT_VALUE
|
||||
m.send(
|
||||
DataPacket(
|
||||
"+16508675310",
|
||||
testPayload,
|
||||
MeshProtos.Data.Type.CLEAR_TEXT_VALUE
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ data class TextMessage(
|
|||
) {
|
||||
/// We can auto init from data packets
|
||||
constructor(payload: DataPacket) : this(
|
||||
payload.from,
|
||||
payload.bytes.toString(utf8),
|
||||
payload.from!!,
|
||||
payload.bytes!!.toString(utf8),
|
||||
date = Date(payload.rxTime)
|
||||
)
|
||||
}
|
||||
|
@ -58,15 +58,17 @@ class MessagesState(private val ui: UIViewModel) : Logging {
|
|||
fun addMessage(payload: DataPacket) = addMessage(TextMessage(payload))
|
||||
|
||||
/// Send a message and added it to our GUI log
|
||||
fun sendMessage(str: String, dest: String? = null) {
|
||||
fun sendMessage(str: String, dest: String = DataPacket.ID_BROADCAST) {
|
||||
var error: String? = null
|
||||
val service = ui.meshService
|
||||
if (service != null)
|
||||
try {
|
||||
service.sendData(
|
||||
dest,
|
||||
str.toByteArray(utf8),
|
||||
MeshProtos.Data.Type.CLEAR_TEXT_VALUE
|
||||
service.send(
|
||||
DataPacket(
|
||||
dest,
|
||||
str.toByteArray(utf8),
|
||||
MeshProtos.Data.Type.CLEAR_TEXT_VALUE
|
||||
)
|
||||
)
|
||||
} catch (ex: RemoteException) {
|
||||
error = "Error: ${ex.message}"
|
||||
|
|
|
@ -395,7 +395,7 @@ class MeshService : Service(), Logging {
|
|||
|
||||
builder.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.bigText(packet.bytes.toString(utf8))
|
||||
.bigText(packet.bytes!!.toString(utf8))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -588,7 +588,8 @@ class MeshService : Service(), Logging {
|
|||
)
|
||||
|
||||
/// Map a nodenum to the nodeid string, or return null if not present or no id found
|
||||
private fun toNodeID(n: Int) = nodeDBbyNodeNum[n]?.user?.id
|
||||
private fun toNodeID(n: Int) =
|
||||
if (n == NODENUM_BROADCAST) DataPacket.ID_BROADCAST else nodeDBbyNodeNum[n]?.user?.id
|
||||
|
||||
/// given a nodenum, return a db entry - creating if necessary
|
||||
private fun getOrCreateNodeInfo(n: Int) =
|
||||
|
@ -609,7 +610,12 @@ class MeshService : Service(), Logging {
|
|||
*/
|
||||
private val numOnlineNodes get() = nodeDBbyNodeNum.values.count { it.isOnline }
|
||||
|
||||
private fun toNodeNum(id: String) = toNodeInfo(id).num
|
||||
private fun toNodeNum(id: String) =
|
||||
when (id) {
|
||||
DataPacket.ID_BROADCAST -> NODENUM_BROADCAST
|
||||
DataPacket.ID_LOCAL -> myNodeNum
|
||||
else -> toNodeInfo(id).num
|
||||
}
|
||||
|
||||
/// A helper function that makes it easy to update node info objects
|
||||
private fun updateNodeInfo(nodeNum: Int, updatefn: (NodeInfo) -> Unit) {
|
||||
|
@ -647,8 +653,8 @@ class MeshService : Service(), Logging {
|
|||
*
|
||||
* If id is null we assume a broadcast message
|
||||
*/
|
||||
private fun newMeshPacketTo(id: String?) =
|
||||
newMeshPacketTo(if (id != null) toNodeNum(id) else NODENUM_BROADCAST)
|
||||
private fun newMeshPacketTo(id: String) =
|
||||
newMeshPacketTo(toNodeNum(id))
|
||||
|
||||
/**
|
||||
* Helper to make it easy to build a subpacket in the proper protobufs
|
||||
|
@ -656,7 +662,7 @@ class MeshService : Service(), Logging {
|
|||
* If destId is null we assume a broadcast message
|
||||
*/
|
||||
private fun buildMeshPacket(
|
||||
destId: String?,
|
||||
destId: String,
|
||||
wantAck: Boolean = false,
|
||||
initFn: MeshProtos.SubPacket.Builder.() -> Unit
|
||||
): MeshPacket = newMeshPacketTo(destId).apply {
|
||||
|
@ -678,27 +684,44 @@ class MeshService : Service(), Logging {
|
|||
val bytes = data.payload.toByteArray()
|
||||
val fromId = toNodeID(packet.from)
|
||||
val toId = toNodeID(packet.to)
|
||||
?: packet.to.toString() // FIXME, we don't currently have IDs specified for the broadcast address
|
||||
|
||||
// If the rxTime was not set by the device (because device software was old), guess at a time
|
||||
val rxTime = if (packet.rxTime == 0) packet.rxTime else currentSecond()
|
||||
|
||||
if (fromId != null) {
|
||||
DataPacket(
|
||||
fromId,
|
||||
toId,
|
||||
rxTime * 1000L,
|
||||
packet.id,
|
||||
data.typValue,
|
||||
bytes
|
||||
)
|
||||
} else {
|
||||
warn("Ignoring data from ${packet.from} because we don't yet know its ID")
|
||||
null
|
||||
when {
|
||||
fromId == null -> {
|
||||
errormsg("Ignoring data from ${packet.from} because we don't yet know its ID")
|
||||
null
|
||||
}
|
||||
toId == null -> {
|
||||
errormsg("Ignoring data to ${packet.to} because we don't yet know its ID")
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
DataPacket(
|
||||
from = fromId,
|
||||
to = toId,
|
||||
rxTime = rxTime * 1000L,
|
||||
id = packet.id,
|
||||
dataType = data.typValue,
|
||||
bytes = bytes
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toMeshPacket(p: DataPacket): MeshPacket {
|
||||
val packet = buildMeshPacket(p.to!!, wantAck = true) {
|
||||
data = MeshProtos.Data.newBuilder().also {
|
||||
it.typ = MeshProtos.Data.Type.forNumber(p.dataType)
|
||||
it.payload = ByteString.copyFrom(p.bytes)
|
||||
}.build()
|
||||
}
|
||||
|
||||
return packet
|
||||
}
|
||||
|
||||
private fun rememberDataPacket(dataPacket: DataPacket) {
|
||||
// discard old messages if needed then add the new one
|
||||
while (recentDataPackets.size > 20) // FIXME, we should instead serialize this list to flash on shutdown
|
||||
|
@ -716,6 +739,7 @@ class MeshService : Service(), Logging {
|
|||
if (dataPacket != null) {
|
||||
debug("Received data from $fromId ${bytes.size}")
|
||||
|
||||
dataPacket.status = MessageStatus.RECEIVED
|
||||
rememberDataPacket(dataPacket)
|
||||
|
||||
when (data.typValue) {
|
||||
|
@ -774,7 +798,7 @@ class MeshService : Service(), Logging {
|
|||
private val earlyReceivedPackets = mutableListOf<MeshPacket>()
|
||||
|
||||
/// If apps try to send packets when our radio is sleeping, we queue them here instead
|
||||
private val offlineSentPackets = mutableListOf<MeshPacket>()
|
||||
private val offlineSentPackets = mutableListOf<DataPacket>()
|
||||
|
||||
/// Update our model and resend as needed for a MeshPacket we just received from the radio
|
||||
private fun handleReceivedMeshPacket(packet: MeshPacket) {
|
||||
|
@ -792,7 +816,12 @@ class MeshService : Service(), Logging {
|
|||
earlyReceivedPackets.forEach { processReceivedMeshPacket(it) }
|
||||
earlyReceivedPackets.clear()
|
||||
|
||||
offlineSentPackets.forEach { sendToRadio(it) }
|
||||
offlineSentPackets.forEach { p ->
|
||||
// encapsulate our payload in the proper protobufs and fire it off
|
||||
val packet = toMeshPacket(p)
|
||||
p.status = MessageStatus.ENROUTE
|
||||
sendToRadio(packet)
|
||||
}
|
||||
offlineSentPackets.clear()
|
||||
}
|
||||
|
||||
|
@ -1347,38 +1376,41 @@ class MeshService : Service(), Logging {
|
|||
this@MeshService.setOwner(myId, longName, shortName)
|
||||
}
|
||||
|
||||
override fun sendData(
|
||||
destId: String?,
|
||||
payloadIn: ByteArray,
|
||||
typ: Int
|
||||
): Boolean =
|
||||
override fun send(
|
||||
p: DataPacket
|
||||
) {
|
||||
toRemoteExceptions {
|
||||
info("sendData dest=$destId <- ${payloadIn.size} bytes (connectionState=$connectionState)")
|
||||
info("sendData dest=${p.to} <- ${p.bytes!!.size} bytes (connectionState=$connectionState)")
|
||||
|
||||
// encapsulate our payload in the proper protobufs and fire it off
|
||||
val packet = buildMeshPacket(destId, wantAck = true) {
|
||||
data = MeshProtos.Data.newBuilder().also {
|
||||
it.typ = MeshProtos.Data.Type.forNumber(typ)
|
||||
it.payload = ByteString.copyFrom(payloadIn)
|
||||
}.build()
|
||||
// FIXME - init from and id in DataPacket
|
||||
myNodeID?.let { myId ->
|
||||
if (p.from == DataPacket.ID_LOCAL)
|
||||
p.from = myId
|
||||
}
|
||||
|
||||
|
||||
// Keep a record of datapackets, so GUIs can show proper chat history
|
||||
toDataPacket(packet)?.let {
|
||||
rememberDataPacket(it)
|
||||
}
|
||||
rememberDataPacket(p)
|
||||
|
||||
// If radio is sleeping, queue the packet
|
||||
when (connectionState) {
|
||||
ConnectionState.DEVICE_SLEEP ->
|
||||
offlineSentPackets.add(packet)
|
||||
else ->
|
||||
ConnectionState.DEVICE_SLEEP -> {
|
||||
p.status = MessageStatus.QUEUED
|
||||
offlineSentPackets.add(p)
|
||||
}
|
||||
else -> {
|
||||
p.status = MessageStatus.ENROUTE
|
||||
|
||||
// encapsulate our payload in the proper protobufs and fire it off
|
||||
val packet = toMeshPacket(p)
|
||||
sendToRadio(packet)
|
||||
}
|
||||
}
|
||||
|
||||
GeeksvilleApplication.analytics.track(
|
||||
"data_send",
|
||||
DataPair("num_bytes", payloadIn.size),
|
||||
DataPair("type", typ)
|
||||
DataPair("num_bytes", p.bytes!!.size),
|
||||
DataPair("type", p.dataType)
|
||||
)
|
||||
|
||||
GeeksvilleApplication.analytics.track(
|
||||
|
@ -1388,6 +1420,7 @@ class MeshService : Service(), Logging {
|
|||
|
||||
connectionState == ConnectionState.CONNECTED
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRadioConfig(): ByteArray = toRemoteExceptions {
|
||||
this@MeshService.radioConfig?.toByteArray()
|
||||
|
@ -1411,4 +1444,4 @@ class MeshService : Service(), Logging {
|
|||
r.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit bfae47bdc0da23bb1e53fed054d3de2d161389bc
|
||||
Subproject commit adf4127fe3e4140bfd97b48a2d11b53ee34a16c8
|
Ładowanie…
Reference in New Issue