sforkowany z mirror/meshtastic-android
mock interface now pretty completely simulates a real device
rodzic
a0160cadf9
commit
7d846461e4
|
@ -606,7 +606,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
|
||||
/// Called when we gain/lose a connection to our mesh radio
|
||||
private fun onMeshConnectionChanged(connected: MeshService.ConnectionState) {
|
||||
debug("connchange ${model.isConnected.value}")
|
||||
debug("connchange ${model.isConnected.value} -> $connected")
|
||||
|
||||
if (connected == MeshService.ConnectionState.CONNECTED) {
|
||||
model.meshService?.let { service ->
|
||||
|
|
|
@ -2,13 +2,13 @@ package com.geeksville.mesh.model
|
|||
|
||||
import android.os.RemoteException
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.geeksville.android.BuildUtils.isEmulator
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
|
||||
|
||||
class MessagesState(private val ui: UIViewModel) : Logging {
|
||||
/* We now provide fake messages a via MockInterface
|
||||
private val testTexts = listOf(
|
||||
DataPacket(
|
||||
"+16508765310",
|
||||
|
@ -18,10 +18,10 @@ class MessagesState(private val ui: UIViewModel) : Logging {
|
|||
"+16508765311",
|
||||
"Help! I've fallen and I can't get up."
|
||||
)
|
||||
)
|
||||
) */
|
||||
|
||||
/// This is the inner storage for messages
|
||||
private val messagesList = (if (isEmulator) testTexts else emptyList()).toMutableList()
|
||||
private val messagesList = emptyList<DataPacket>().toMutableList()
|
||||
|
||||
// If the following (unused otherwise) line is commented out, the IDE preview window works.
|
||||
// if left in the preview always renders as empty.
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.geeksville.mesh.model
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.geeksville.android.BuildUtils.isEmulator
|
||||
import com.geeksville.mesh.MeshUser
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import com.geeksville.mesh.Position
|
||||
|
@ -43,15 +42,15 @@ class NodeDB(private val ui: UIViewModel) {
|
|||
)
|
||||
}
|
||||
|
||||
private val seedWithTestNodes = isEmulator
|
||||
private val seedWithTestNodes = false
|
||||
|
||||
/// The unique ID of our node
|
||||
val myId = object : MutableLiveData<String?>(if (isEmulator) "+16508765309" else null) {}
|
||||
val myId = object : MutableLiveData<String?>(if (seedWithTestNodes) "+16508765309" else null) {}
|
||||
|
||||
/// A map from nodeid to to nodeinfo
|
||||
val nodes =
|
||||
object :
|
||||
MutableLiveData<Map<String, NodeInfo>>(mapOf(*(if (isEmulator) testNodes else listOf()).map { it.user!!.id to it }
|
||||
MutableLiveData<Map<String, NodeInfo>>(mapOf(*(if (seedWithTestNodes) testNodes else listOf()).map { it.user!!.id to it }
|
||||
.toTypedArray())) {}
|
||||
|
||||
/// Could be null if we haven't received our node DB yet
|
||||
|
|
|
@ -11,7 +11,6 @@ import androidx.lifecycle.AndroidViewModel
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.geeksville.android.BuildUtils.isEmulator
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.IMeshService
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
|
@ -77,10 +76,7 @@ class UIViewModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
fun getChannel(c: MeshProtos.RadioConfig?): Channel? {
|
||||
val channel = c?.channelSettings?.let { Channel(it) }
|
||||
|
||||
return if (channel == null && isEmulator)
|
||||
Channel.emulated
|
||||
else
|
||||
channel
|
||||
return channel
|
||||
}
|
||||
|
||||
fun getPreferences(context: Context): SharedPreferences =
|
||||
|
|
|
@ -2,38 +2,109 @@ package com.geeksville.mesh.service
|
|||
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.Portnums
|
||||
import com.geeksville.mesh.Position
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.getInitials
|
||||
import com.google.protobuf.ByteString
|
||||
import okhttp3.internal.toHexString
|
||||
|
||||
/** A simulated interface that is used for testing in the simulator */
|
||||
class MockInterface(private val service: RadioInterfaceService) : Logging, IRadioInterface {
|
||||
companion object : Logging {
|
||||
|
||||
val interfaceName = "m"
|
||||
const val interfaceName = "m"
|
||||
}
|
||||
|
||||
private var messageCount = 50
|
||||
|
||||
// an infinite sequence of ints
|
||||
private val messageNumSequence = generateSequence { messageCount++ }.iterator()
|
||||
|
||||
init {
|
||||
info("Starting the mock interface")
|
||||
service.onConnect() // Tell clients they can use the API
|
||||
}
|
||||
|
||||
override fun handleSendToRadio(b: ByteArray) {
|
||||
val p = MeshProtos.ToRadio.parseFrom(b)
|
||||
override fun handleSendToRadio(p: ByteArray) {
|
||||
val pr = MeshProtos.ToRadio.parseFrom(p)
|
||||
|
||||
if (p.wantConfigId != 0)
|
||||
sendConfigResponse(p.wantConfigId)
|
||||
else
|
||||
info("Ignoring data sent to mock interface $p")
|
||||
when {
|
||||
pr.wantConfigId != 0 -> sendConfigResponse(pr.wantConfigId)
|
||||
pr.hasPacket() && pr.packet.wantAck -> sendFakeAck(pr)
|
||||
else -> info("Ignoring data sent to mock interface $pr")
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
info("Closing the mock interface")
|
||||
}
|
||||
|
||||
/// Generate a fake text message from a node
|
||||
private fun makeTextMessage(numIn: Int) =
|
||||
MeshProtos.FromRadio.newBuilder().apply {
|
||||
packet = MeshProtos.MeshPacket.newBuilder().apply {
|
||||
id = messageNumSequence.next()
|
||||
from = numIn
|
||||
to = 0xffffffff.toInt() // ugly way of saying broadcast
|
||||
rxTime = (System.currentTimeMillis() / 1000).toInt()
|
||||
rxSnr = 1.5f
|
||||
decoded = MeshProtos.SubPacket.newBuilder().apply {
|
||||
data = MeshProtos.Data.newBuilder().apply {
|
||||
portnum = Portnums.PortNum.TEXT_MESSAGE_APP
|
||||
payload = ByteString.copyFromUtf8("This simulated node sends Hi!")
|
||||
}.build()
|
||||
}.build()
|
||||
}.build()
|
||||
}
|
||||
|
||||
|
||||
private fun makeAck(fromIn: Int, toIn: Int, msgId: Int) =
|
||||
MeshProtos.FromRadio.newBuilder().apply {
|
||||
packet = MeshProtos.MeshPacket.newBuilder().apply {
|
||||
id = messageNumSequence.next()
|
||||
from = fromIn
|
||||
to = toIn
|
||||
rxTime = (System.currentTimeMillis() / 1000).toInt()
|
||||
rxSnr = 1.5f
|
||||
decoded = MeshProtos.SubPacket.newBuilder().apply {
|
||||
data = MeshProtos.Data.newBuilder().apply {
|
||||
successId = msgId
|
||||
}.build()
|
||||
}.build()
|
||||
}.build()
|
||||
}
|
||||
|
||||
/// Send a fake ack packet back if the sender asked for want_ack
|
||||
private fun sendFakeAck(pr: MeshProtos.ToRadio) {
|
||||
service.handleFromRadio(makeAck(pr.packet.to, pr.packet.from, pr.packet.id).build().toByteArray())
|
||||
}
|
||||
|
||||
private fun sendConfigResponse(configId: Int) {
|
||||
debug("Sending mock config response")
|
||||
|
||||
/// Generate a fake node info entry
|
||||
fun makeNodeInfo(numIn: Int, lat: Double, lon: Double) =
|
||||
MeshProtos.FromRadio.newBuilder().apply {
|
||||
nodeInfo = MeshProtos.NodeInfo.newBuilder().apply {
|
||||
num = numIn
|
||||
user = MeshProtos.User.newBuilder().apply {
|
||||
id = "!0x" + num.toHexString()
|
||||
longName = "Sim " + num.toHexString()
|
||||
shortName = getInitials(longName)
|
||||
}.build()
|
||||
position = MeshProtos.Position.newBuilder().apply {
|
||||
latitudeI = Position.degI(lat)
|
||||
longitudeI = Position.degI(lon)
|
||||
batteryLevel = 42
|
||||
altitude = 35
|
||||
time = (System.currentTimeMillis() / 1000).toInt()
|
||||
}.build()
|
||||
}.build()
|
||||
}
|
||||
|
||||
// Simulated network data to feed to our app
|
||||
val MY_NODE = 0x42424242
|
||||
val MY_NODE = 0x42424242
|
||||
val packets = arrayOf(
|
||||
// MyNodeInfo
|
||||
MeshProtos.FromRadio.newBuilder().apply {
|
||||
|
@ -56,40 +127,30 @@ class MockInterface(private val service: RadioInterfaceService) : Logging, IRadi
|
|||
|
||||
preferences = MeshProtos.RadioConfig.UserPreferences.newBuilder().apply {
|
||||
region = MeshProtos.RegionCode.TW
|
||||
// FIXME set critical times
|
||||
// FIXME set critical times?
|
||||
}.build()
|
||||
|
||||
channel = MeshProtos.ChannelSettings.newBuilder().apply {
|
||||
// fixme() // fix channel display
|
||||
// fix testlab // (application as GeeksvilleApplication).isInTestLab
|
||||
// we just have an empty listing so that the default channel works
|
||||
}.build()
|
||||
}.build()
|
||||
},
|
||||
|
||||
// Fake NodeDB
|
||||
MeshProtos.FromRadio.newBuilder().apply {
|
||||
nodeInfo = MeshProtos.NodeInfo.newBuilder().apply {
|
||||
num = MY_NODE
|
||||
user = MeshProtos.User.newBuilder().apply {
|
||||
id = "!0x42424242"
|
||||
longName = "Sim User"
|
||||
shortName = "SU"
|
||||
}.build()
|
||||
position = MeshProtos.Position.newBuilder().apply {
|
||||
latitudeI = 42
|
||||
longitudeI = 42 // FIXME
|
||||
time = 42 // FIXME
|
||||
}.build()
|
||||
}.build()
|
||||
},
|
||||
makeNodeInfo(MY_NODE, 32.776665, -96.796989), // dallas
|
||||
makeNodeInfo(MY_NODE + 1, 32.960758, -96.733521), // richardson
|
||||
|
||||
MeshProtos.FromRadio.newBuilder().apply {
|
||||
configCompleteId = configId
|
||||
}
|
||||
},
|
||||
|
||||
// Done with config response, now pretend to receive some text messages
|
||||
|
||||
makeTextMessage(MY_NODE + 1)
|
||||
)
|
||||
|
||||
packets.forEach { p ->
|
||||
service.handleFromRadio(p.build().toByteArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,12 +86,16 @@ class RadioInterfaceService : Service(), Logging {
|
|||
}
|
||||
|
||||
// If we are running on the emulator we default to the mock interface, so we can have some data to show to the user
|
||||
if(address == null && isEmulator)
|
||||
if(address == null && isMockInterfaceAvailable(context))
|
||||
address = MockInterface.interfaceName
|
||||
|
||||
return address
|
||||
}
|
||||
|
||||
/** return true if we should show the mock interface on this device
|
||||
* (ie are we in an emulator or in testlab
|
||||
*/
|
||||
fun isMockInterfaceAvailable(context: Context) = isEmulator || ((context.applicationContext as GeeksvilleApplication).isInTestLab)
|
||||
|
||||
/** Like getDeviceAddress, but filtered to return only devices we are currently bonded with
|
||||
*
|
||||
|
|
|
@ -265,7 +265,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
debug("BTScan component active")
|
||||
selectedAddress = RadioInterfaceService.getDeviceAddress(context)
|
||||
|
||||
return if (bluetoothAdapter == null) {
|
||||
return if (bluetoothAdapter == null || RadioInterfaceService.isMockInterfaceAvailable(context)) {
|
||||
warn("No bluetooth adapter. Running under emulation?")
|
||||
|
||||
val testnodes = listOf(
|
||||
|
|
Ładowanie…
Reference in New Issue