diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index ebd378a0..8f6f23f3 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -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 -> diff --git a/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt b/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt index 5400937c..dd882880 100644 --- a/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt @@ -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().toMutableList() // If the following (unused otherwise) line is commented out, the IDE preview window works. // if left in the preview always renders as empty. diff --git a/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt b/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt index f3e31eaf..d2f35f5f 100644 --- a/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt +++ b/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt @@ -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(if (isEmulator) "+16508765309" else null) {} + val myId = object : MutableLiveData(if (seedWithTestNodes) "+16508765309" else null) {} /// A map from nodeid to to nodeinfo val nodes = object : - MutableLiveData>(mapOf(*(if (isEmulator) testNodes else listOf()).map { it.user!!.id to it } + MutableLiveData>(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 diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index eb7ee53b..40ee1f4e 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -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 = diff --git a/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt b/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt index df59052a..ade70bf6 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt @@ -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()) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt index 9117bde4..c24e5aa8 100644 --- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt @@ -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 * diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index 0399cc03..26cb3263 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -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(