kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
allow sending broadcasts and cope with missing mesh services
rodzic
e738b7692f
commit
165df2c4de
|
@ -28,6 +28,8 @@ interface IMeshService {
|
||||||
Send an opaque packet to a specified node name
|
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.
|
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"
|
||||||
*/
|
*/
|
||||||
void sendData(String destId, in byte[] payload, int typ);
|
void sendData(String destId, in byte[] payload, int typ);
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,9 @@ eventually:
|
||||||
make a custom theme: https://github.com/material-components/material-components-android/tree/master/material-theme-builder
|
make a custom theme: https://github.com/material-components/material-components-android/tree/master/material-theme-builder
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
val utf8 = Charset.forName("UTF-8")
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), Logging,
|
class MainActivity : AppCompatActivity(), Logging,
|
||||||
ActivityCompat.OnRequestPermissionsResultCallback {
|
ActivityCompat.OnRequestPermissionsResultCallback {
|
||||||
|
|
||||||
|
@ -90,8 +93,6 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val utf8 = Charset.forName("UTF-8")
|
|
||||||
|
|
||||||
private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) {
|
private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
bluetoothManager.adapter
|
bluetoothManager.adapter
|
||||||
|
@ -154,12 +155,12 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||||
private fun setOwner() {
|
private fun setOwner() {
|
||||||
// Note: we are careful to not set a new unique ID
|
// Note: we are careful to not set a new unique ID
|
||||||
val name = UIState.ownerName.value
|
val name = UIState.ownerName.value
|
||||||
meshService!!.setOwner(null, name, getInitials(name))
|
UIState.meshService!!.setOwner(null, name, getInitials(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendTestPackets() {
|
private fun sendTestPackets() {
|
||||||
exceptionReporter {
|
exceptionReporter {
|
||||||
val m = meshService!!
|
val m = UIState.meshService!!
|
||||||
|
|
||||||
// Do some test operations
|
// Do some test operations
|
||||||
val testPayload = "hello world".toByteArray()
|
val testPayload = "hello world".toByteArray()
|
||||||
|
@ -270,7 +271,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||||
|
|
||||||
/// Read the config bytes from the radio so we can show them in our GUI, the radio's copy is ground truth
|
/// Read the config bytes from the radio so we can show them in our GUI, the radio's copy is ground truth
|
||||||
private fun readRadioConfig() {
|
private fun readRadioConfig() {
|
||||||
val bytes = meshService!!.radioConfig
|
val bytes = UIState.meshService!!.radioConfig
|
||||||
|
|
||||||
val config = MeshProtos.RadioConfig.parseFrom(bytes)
|
val config = MeshProtos.RadioConfig.parseFrom(bytes)
|
||||||
UIState.radioConfig.value = config
|
UIState.radioConfig.value = config
|
||||||
|
@ -340,14 +341,13 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var meshService: IMeshService? = null
|
|
||||||
private var isBound = false
|
private var isBound = false
|
||||||
|
|
||||||
private var serviceConnection = object : ServiceConnection {
|
private var serviceConnection = object : ServiceConnection {
|
||||||
override fun onServiceConnected(name: ComponentName, service: IBinder) =
|
override fun onServiceConnected(name: ComponentName, service: IBinder) =
|
||||||
exceptionReporter {
|
exceptionReporter {
|
||||||
val m = IMeshService.Stub.asInterface(service)
|
val m = IMeshService.Stub.asInterface(service)
|
||||||
meshService = m
|
UIState.meshService = m
|
||||||
|
|
||||||
// We don't start listening for packets until after we are connected to the service
|
// We don't start listening for packets until after we are connected to the service
|
||||||
registerMeshReceiver()
|
registerMeshReceiver()
|
||||||
|
@ -367,14 +367,14 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||||
override fun onServiceDisconnected(name: ComponentName) {
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
warn("The mesh service has disconnected")
|
warn("The mesh service has disconnected")
|
||||||
unregisterMeshReceiver()
|
unregisterMeshReceiver()
|
||||||
meshService = null
|
UIState.meshService = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindMeshService() {
|
private fun bindMeshService() {
|
||||||
debug("Binding to mesh service!")
|
debug("Binding to mesh service!")
|
||||||
// we bind using the well known name, to make sure 3rd party apps could also
|
// we bind using the well known name, to make sure 3rd party apps could also
|
||||||
logAssert(meshService == null)
|
logAssert(UIState.meshService == null)
|
||||||
|
|
||||||
val intent = MeshService.startService(this)
|
val intent = MeshService.startService(this)
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
|
@ -391,7 +391,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||||
debug("Unbinding from mesh service!")
|
debug("Unbinding from mesh service!")
|
||||||
if (isBound)
|
if (isBound)
|
||||||
unbindService(serviceConnection)
|
unbindService(serviceConnection)
|
||||||
meshService = null
|
UIState.meshService = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
|
|
@ -89,21 +89,22 @@ data class NodeInfo(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @return distance in meters to some other node (or null if unknown)
|
/// @return distance in meters to some other node (or null if unknown)
|
||||||
fun distance(o: NodeInfo?): Double? {
|
fun distance(o: NodeInfo?): Int? {
|
||||||
val p = position
|
val p = position
|
||||||
val op = o?.position
|
val op = o?.position
|
||||||
return if (p != null && op != null)
|
return if (p != null && op != null)
|
||||||
p.distance(op)
|
p.distance(op).toInt()
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @return a nice human readable string for the distance, or null for unknown
|
/// @return a nice human readable string for the distance, or null for unknown
|
||||||
fun distanceStr(o: NodeInfo?) = distance(o)?.let { dist ->
|
fun distanceStr(o: NodeInfo?) = distance(o)?.let { dist ->
|
||||||
if (dist < 1000)
|
when {
|
||||||
"%.0f m".format(dist)
|
dist == 0 -> null // same point
|
||||||
else
|
dist < 1000 -> "%.0f m".format(dist)
|
||||||
"%.1f km".format(dist / 1000)
|
else -> "%.1f km".format(dist / 1000.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
|
|
@ -6,8 +6,15 @@ import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the model object for a text message
|
* the model object for a text message
|
||||||
|
*
|
||||||
|
* if errorMessage is set then we had a problem sending this message
|
||||||
*/
|
*/
|
||||||
data class TextMessage(val from: String, val text: String, val date: Date = Date())
|
data class TextMessage(
|
||||||
|
val from: String,
|
||||||
|
val text: String,
|
||||||
|
val date: Date = Date(),
|
||||||
|
val errorMessage: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
object MessagesState : Logging {
|
object MessagesState : Logging {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.geeksville.mesh.model
|
||||||
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import androidx.compose.mutableStateOf
|
import androidx.compose.mutableStateOf
|
||||||
|
import com.geeksville.mesh.IMeshService
|
||||||
import com.geeksville.mesh.MeshProtos
|
import com.geeksville.mesh.MeshProtos
|
||||||
|
|
||||||
/// FIXME - figure out how to merge this staate with the AppStatus Model
|
/// FIXME - figure out how to merge this staate with the AppStatus Model
|
||||||
|
@ -10,6 +11,7 @@ object UIState {
|
||||||
/// Kinda ugly - created in the activity but used from Compose - figure out if there is a cleaner way GIXME
|
/// Kinda ugly - created in the activity but used from Compose - figure out if there is a cleaner way GIXME
|
||||||
// lateinit var googleSignInClient: GoogleSignInClient
|
// lateinit var googleSignInClient: GoogleSignInClient
|
||||||
|
|
||||||
|
var meshService: IMeshService? = null
|
||||||
|
|
||||||
/// Are we connected to our radio device
|
/// Are we connected to our radio device
|
||||||
val isConnected = mutableStateOf(false)
|
val isConnected = mutableStateOf(false)
|
||||||
|
|
|
@ -418,12 +418,21 @@ class MeshService : Service(), Logging {
|
||||||
to = idNum
|
to = idNum
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a new mesh packet builder with our node as the sender, and the specified recipient
|
/**
|
||||||
private fun newMeshPacketTo(id: String) = newMeshPacketTo(toNodeNum(id))
|
* Generate a new mesh packet builder with our node as the sender, and the specified recipient
|
||||||
|
*
|
||||||
|
* If id is null we assume a broadcast message
|
||||||
|
*/
|
||||||
|
private fun newMeshPacketTo(id: String?) =
|
||||||
|
newMeshPacketTo(if (id != null) toNodeNum(id) else NODENUM_BROADCAST)
|
||||||
|
|
||||||
// Helper to make it easy to build a subpacket in the proper protobufs
|
/**
|
||||||
|
* Helper to make it easy to build a subpacket in the proper protobufs
|
||||||
|
*
|
||||||
|
* If destId is null we assume a broadcast message
|
||||||
|
*/
|
||||||
private fun buildMeshPacket(
|
private fun buildMeshPacket(
|
||||||
destId: String,
|
destId: String?,
|
||||||
initFn: MeshProtos.SubPacket.Builder.() -> Unit
|
initFn: MeshProtos.SubPacket.Builder.() -> Unit
|
||||||
): MeshPacket = newMeshPacketTo(destId).apply {
|
): MeshPacket = newMeshPacketTo(destId).apply {
|
||||||
payload = MeshProtos.SubPacket.newBuilder().also {
|
payload = MeshProtos.SubPacket.newBuilder().also {
|
||||||
|
@ -687,9 +696,9 @@ class MeshService : Service(), Logging {
|
||||||
connectedRadio.writeOwner(user.toByteArray())
|
connectedRadio.writeOwner(user.toByteArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendData(destId: String, payloadIn: ByteArray, typ: Int) =
|
override fun sendData(destId: String?, payloadIn: ByteArray, typ: Int) =
|
||||||
toRemoteExceptions {
|
toRemoteExceptions {
|
||||||
info("sendData $destId <- ${payloadIn.size} bytes")
|
info("sendData dest=$destId <- ${payloadIn.size} bytes")
|
||||||
|
|
||||||
// encapsulate our payload in the proper protobufs and fire it off
|
// encapsulate our payload in the proper protobufs and fire it off
|
||||||
val packet = buildMeshPacket(destId) {
|
val packet = buildMeshPacket(destId) {
|
||||||
|
|
|
@ -20,10 +20,13 @@ import androidx.ui.material.surface.Surface
|
||||||
import androidx.ui.text.TextStyle
|
import androidx.ui.text.TextStyle
|
||||||
import androidx.ui.tooling.preview.Preview
|
import androidx.ui.tooling.preview.Preview
|
||||||
import androidx.ui.unit.dp
|
import androidx.ui.unit.dp
|
||||||
|
import com.geeksville.mesh.MeshProtos
|
||||||
import com.geeksville.mesh.model.MessagesState
|
import com.geeksville.mesh.model.MessagesState
|
||||||
import com.geeksville.mesh.model.MessagesState.messages
|
import com.geeksville.mesh.model.MessagesState.messages
|
||||||
import com.geeksville.mesh.model.NodeDB
|
import com.geeksville.mesh.model.NodeDB
|
||||||
import com.geeksville.mesh.model.TextMessage
|
import com.geeksville.mesh.model.TextMessage
|
||||||
|
import com.geeksville.mesh.model.UIState
|
||||||
|
import com.geeksville.mesh.utf8
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,11 +61,11 @@ fun MessageCard(msg: TextMessage, modifier: Modifier = Modifier.None) {
|
||||||
style = MaterialTheme.typography().caption
|
style = MaterialTheme.typography().caption
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Text(
|
if (msg.errorMessage != null)
|
||||||
text = msg.text
|
Text(text = msg.errorMessage, style = TextStyle(color = palette.error))
|
||||||
)
|
else
|
||||||
|
Text(text = msg.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,10 +112,25 @@ fun MessagesContent() {
|
||||||
imeAction = ImeAction.Send,
|
imeAction = ImeAction.Send,
|
||||||
onImeActionPerformed = {
|
onImeActionPerformed = {
|
||||||
MessagesState.info("did IME action")
|
MessagesState.info("did IME action")
|
||||||
|
|
||||||
|
val str = message.value
|
||||||
|
|
||||||
|
var error: String? = null
|
||||||
|
val service = UIState.meshService
|
||||||
|
if (service != null)
|
||||||
|
service.sendData(
|
||||||
|
null,
|
||||||
|
str.toByteArray(utf8),
|
||||||
|
MeshProtos.Data.Type.CLEAR_TEXT_VALUE
|
||||||
|
)
|
||||||
|
else
|
||||||
|
error = "Error: No Mesh service"
|
||||||
|
|
||||||
MessagesState.addMessage(
|
MessagesState.addMessage(
|
||||||
TextMessage(
|
TextMessage(
|
||||||
"fixme",
|
NodeDB.myId.value,
|
||||||
message.value
|
str,
|
||||||
|
errorMessage = error
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,8 +5,10 @@ import androidx.ui.core.Modifier
|
||||||
import androidx.ui.core.Text
|
import androidx.ui.core.Text
|
||||||
import androidx.ui.layout.Column
|
import androidx.ui.layout.Column
|
||||||
import androidx.ui.layout.LayoutGravity
|
import androidx.ui.layout.LayoutGravity
|
||||||
|
import androidx.ui.layout.LayoutWidth
|
||||||
import androidx.ui.material.MaterialTheme
|
import androidx.ui.material.MaterialTheme
|
||||||
import androidx.ui.tooling.preview.Preview
|
import androidx.ui.tooling.preview.Preview
|
||||||
|
import androidx.ui.unit.dp
|
||||||
import com.geeksville.mesh.NodeInfo
|
import com.geeksville.mesh.NodeInfo
|
||||||
import com.geeksville.mesh.R
|
import com.geeksville.mesh.R
|
||||||
import com.geeksville.mesh.model.NodeDB
|
import com.geeksville.mesh.model.NodeDB
|
||||||
|
@ -14,10 +16,12 @@ import com.geeksville.mesh.model.NodeDB
|
||||||
/**
|
/**
|
||||||
* Show the user icon for a particular user with distance from the operator and a small pointer
|
* Show the user icon for a particular user with distance from the operator and a small pointer
|
||||||
* indicating their direction
|
* indicating their direction
|
||||||
|
*
|
||||||
|
* This component is fixed width to simplify layouts.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun UserIcon(user: NodeInfo? = null, modifier: Modifier = Modifier.None) {
|
fun UserIcon(user: NodeInfo? = null, modifier: Modifier = Modifier.None) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier + LayoutWidth(60.dp)) {
|
||||||
VectorImage(
|
VectorImage(
|
||||||
id = R.drawable.ic_twotone_person_24,
|
id = R.drawable.ic_twotone_person_24,
|
||||||
tint = palette.onSecondary,
|
tint = palette.onSecondary,
|
||||||
|
|
Ładowanie…
Reference in New Issue