From 38119a61f6c07a7f808fcdfae14be75b4ec37c66 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 25 Jan 2020 12:24:53 -0800 Subject: [PATCH] more test cases pass - standard startup packets work --- TODO.md | 8 +-- .../java/com/geeksville/mesh/MeshService.kt | 22 ++++++- .../geeksville/mesh/RadioInterfaceService.kt | 60 +++---------------- .../main/java/com/geeksville/mesh/SimRadio.kt | 51 ++++++++++++++++ app/src/main/proto/mesh.proto | 22 +++++-- 5 files changed, 100 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/SimRadio.kt diff --git a/TODO.md b/TODO.md index 1bb2093d7..324e3312a 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,5 @@ -* receive fake packets at power on to built initial state (for debugging, pretend there are a couple of nodes out there) -* learn our node number -* test mesh service from activity + * use android service from Signal * DONE handle failures in onCharWrite, instead of logAssert - because they can happen if device goes away * make test implementation of android service (doesn't use bluetooth) @@ -52,4 +50,6 @@ Don't leave device discoverable. Don't let unpaired users do thing with device * change bluetooth mtu length to 512 (default is only 20) * DONE get signal running under debugger * Find good Signal hooks - +* receive fake packets at power on to built initial state (for debugging, pretend there are a couple of nodes out there) +* learn our node number +* test mesh service from activity diff --git a/app/src/main/java/com/geeksville/mesh/MeshService.kt b/app/src/main/java/com/geeksville/mesh/MeshService.kt index 830b64f25..d435e26d8 100644 --- a/app/src/main/java/com/geeksville/mesh/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/MeshService.kt @@ -26,6 +26,14 @@ class MeshService : Service(), Logging { companion object { 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") + class RadioNotConnectedException() : Exception("Can't find radio") + + /// If we haven't yet received a node number from the radio + private const val NODE_NUM_UNKNOWN = -2 + + /// If the radio hasn't yet joined a mesh (i.e. no nodenum assigned) + private const val NODE_NUM_NO_MESH = -1 } /* @@ -74,6 +82,9 @@ class MeshService : Service(), Logging { registerReceiver(radioInterfaceReceiver, filter) // FIXME - don't do this until after we see that the radio is connected to the phone + val sim = SimRadio(this) + sim.start() // Fake up our node id info and some past packets from other nodes + // Ask for the current node DB sendToRadio(ToRadio.newBuilder().apply { wantNodes = ToRadio.WantNodes.newBuilder().build() @@ -106,7 +117,7 @@ class MeshService : Service(), Logging { private var isConnected = false /// We learn this from the node db sent by the device - it is stable for the entire session - private var ourNodeNum = -1 + private var ourNodeNum = NODE_NUM_UNKNOWN // The database of active nodes, index is the node number private val nodeDBbyNodeNum = mutableMapOf() @@ -147,6 +158,12 @@ class MeshService : Service(), Logging { /// Generate a new mesh packet builder with our node as the sender, and the specified node num private fun newMeshPacketTo(idNum: Int) = MeshPacket.newBuilder().apply { from = ourNodeNum + + if (from == NODE_NUM_NO_MESH) + throw NotInMeshException() + else if (from == NODE_NUM_UNKNOWN) + throw RadioNotConnectedException() + to = idNum } @@ -264,6 +281,7 @@ class MeshService : Service(), Logging { when (proto.variantCase.number) { MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket(proto.packet) MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> handleReceivedNodeInfo(proto.nodeInfo) + MeshProtos.FromRadio.MY_NODE_NUM_FIELD_NUMBER -> ourNodeNum = proto.myNodeNum else -> TODO("Unexpected FromRadio variant") } } @@ -284,7 +302,7 @@ class MeshService : Service(), Logging { }.build() // Also update our own map for our nodenum, by handling the packet just like packets from other users - if (ourNodeNum != -1) { + if (ourNodeNum >= 0) { handleReceivedUser(ourNodeNum, user) } diff --git a/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt index faa37e4d0..a9148a08f 100644 --- a/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt @@ -55,52 +55,17 @@ class RadioInterfaceService : JobIntentService(), Logging { // for debug logging only private val jsonPrinter = JsonFormat.printer() - private val jsonParser = JsonFormat.parser() - /** - * When simulating we parse these MeshPackets as if they arrived at startup - * Send broadcast them after we receive a ToRadio.WantNodes message. - * - * Our fake net has three nodes - * - * +16508675309, nodenum 9 - our node - * +16508675310, nodenum 10 - some other node, name Bob One/BO - * (eventually) +16508675311, nodenum 11 - some other node - */ - /* - -2020-01-25 11:02:05.279 1162-1280/com.geeksville.mesh D/com.geeksville.mesh.RadioInterfaceService: Executing work: Intent { act=com.geeksville.mesh.SEND_TORADIO (has extras) } -2020-01-25 11:02:05.282 1162-1273/com.geeksville.mesh D/EGL_emulation: eglMakeCurrent: 0xebb2b500: ver 2 0 (tinfo 0xc9748bc0) -2020-01-25 11:02:05.449 1162-1280/com.geeksville.mesh I/com.geeksville.mesh.RadioInterfaceService: TODO sending to radio: { "wantNodes": { } } -2020-01-25 11:02:05.452 1162-1280/com.geeksville.mesh D/com.geeksville.mesh.RadioInterfaceService: Executing work: Intent { act=com.geeksville.mesh.SEND_TORADIO (has extras) } -2020-01-25 11:02:05.479 1162-1280/com.geeksville.mesh I/com.geeksville.mesh.RadioInterfaceService: TODO sending to radio: { "setOwner": { "id": "+16508675309", "longName": "Kevin Xter", "shortName": "kx" } } -2020-01-25 11:02:05.480 1162-1280/com.geeksville.mesh D/com.geeksville.mesh.RadioInterfaceService: Executing work: Intent { act=com.geeksville.mesh.SEND_TORADIO (has extras) } -2020-01-25 11:02:05.504 1162-1280/com.geeksville.mesh I/com.geeksville.mesh.RadioInterfaceService: TODO sending to radio: { "packet": { "from": -1, "to": 10, "payload": { "subPackets": [{ "data": { "payload": "aGVsbG8gd29ybGQ=" } }] } } } -2020-01-25 11:02:05.505 1162-1280/com.geeksville.mesh D/com.geeksville.mesh.RadioInterfaceService: Executing work: Intent { act=com.geeksville.mesh.SEND_TORADIO (has extras) } -2020-01-25 11:02:05.510 1162-1280/com.geeksville.mesh I/com.geeksville.mesh.RadioInterfaceService: TODO sending to radio: { "packet": { "from": -1, "to": 10, "payload": { "subPackets": [{ "data": { "payload": "aGVsbG8gd29ybGQ=" } }] } } } -2020-01-25 11:02:08.232 1162-1273/com.geeksville.mesh D/EGL_emulation: eglMakeCurrent: 0xebb2b500: ver 2 0 (tinfo 0xc9748bc0) - - */ - val simInitPackets = - arrayOf( - """ { "from": 10, "to": 9, "payload": { "subPackets": [{ "user": { "id": "+16508675310", "longName": "Bob One", "shortName": "BO" }}]}} """, - """ { "from": 10, "to": 9, "payload": { "subPackets": [{ "data": { "payload": "aGVsbG8gd29ybGQ=", "typ": 0 }}]}} """, // SIGNAL_OPAQUE - """ { "from": 10, "to": 9, "payload": { "subPackets": [{ "data": { "payload": "aGVsbG8gd29ybGQ=", "typ": 1 }}]}} """, // CLEAR_TEXT - """ { "from": 10, "to": 9, "payload": { "subPackets": [{ "data": { "payload": "", "typ": 2 }}]}} """ // CLEAR_READACK - ) - - // FIXME, move into a subclass? - val isSimulating = true + /// This is public only so that SimRadio can bootstrap our message flow + fun broadcastReceivedFromRadio(context: Context, payload: ByteArray) { + val intent = Intent(RECEIVE_FROMRADIO_ACTION) + intent.putExtra(EXTRA_PAYLOAD, payload) + context.sendBroadcast(intent) + } } lateinit var sentPacketsLog: DebugLogFile // inited in onCreate - private fun broadcastReceivedFromRadio(payload: ByteArray) { - val intent = Intent(RECEIVE_FROMRADIO_ACTION) - intent.putExtra("$prefix.Payload", payload) - sendBroadcast(intent) - } - fun broadcastConnectionChanged(isConnected: Boolean) { val intent = Intent("$prefix.CONNECTION_CHANGED") intent.putExtra(EXTRA_CONNECTED, isConnected) @@ -116,22 +81,11 @@ class RadioInterfaceService : JobIntentService(), Logging { val json = jsonPrinter.print(proto).replace('\n', ' ') info("TODO sending to radio: $json") sentPacketsLog.log(json) - - if (isSimulating) - simInitPackets.forEach { json -> - val fromRadio = MeshProtos.FromRadio.newBuilder().apply { - packet = MeshProtos.MeshPacket.newBuilder().apply { - jsonParser.merge(json, this) - }.build() - }.build() - - broadcastReceivedFromRadio(fromRadio.toByteArray()) - } } // Handle an incoming packet from the radio, broadcasts it as an android intent private fun handleFromRadio(p: ByteArray) { - broadcastReceivedFromRadio(p) + broadcastReceivedFromRadio(this, p) } override fun onCreate() { diff --git a/app/src/main/java/com/geeksville/mesh/SimRadio.kt b/app/src/main/java/com/geeksville/mesh/SimRadio.kt new file mode 100644 index 000000000..1bb25c501 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/SimRadio.kt @@ -0,0 +1,51 @@ +package com.geeksville.mesh + +import android.content.Context +import com.google.protobuf.util.JsonFormat + +class SimRadio(private val context: Context) { + private val jsonParser = JsonFormat.parser() + + /** + * When simulating we parse these MeshPackets as if they arrived at startup + * Send broadcast them after we receive a ToRadio.WantNodes message. + * + * Our fake net has three nodes + * + * +16508675309, nodenum 9 - our node + * +16508675310, nodenum 10 - some other node, name Bob One/BO + * (eventually) +16508675311, nodenum 11 - some other node + */ + private val simInitPackets = + arrayOf( + """ { "from": 10, "to": 9, "payload": { "subPackets": [{ "user": { "id": "+16508675310", "longName": "Bob One", "shortName": "BO" }}]}} """, + """ { "from": 10, "to": 9, "payload": { "subPackets": [{ "data": { "payload": "aGVsbG8gd29ybGQ=", "typ": 0 }}]}} """, // SIGNAL_OPAQUE + """ { "from": 10, "to": 9, "payload": { "subPackets": [{ "data": { "payload": "aGVsbG8gd29ybGQ=", "typ": 1 }}]}} """, // CLEAR_TEXT + """ { "from": 10, "to": 9, "payload": { "subPackets": [{ "data": { "payload": "", "typ": 2 }}]}} """ // CLEAR_READACK + ) + + fun start() { + // FIXME, do this sim startup elsewhere, because waiting for a packet from MeshService + // isn't right, because that service can't successfully send radio packets until it knows + // our node num + // Instead a separate sim radio thing can come in at startup and force these broadcasts to happen + // at the right time + // Send a fake my_node_num response + RadioInterfaceService.broadcastReceivedFromRadio( + context, + MeshProtos.FromRadio.newBuilder().apply { + myNodeNum = 9 + }.build().toByteArray() + ) + + simInitPackets.forEach { json -> + val fromRadio = MeshProtos.FromRadio.newBuilder().apply { + packet = MeshProtos.MeshPacket.newBuilder().apply { + jsonParser.merge(json, this) + }.build() + }.build() + + RadioInterfaceService.broadcastReceivedFromRadio(context, fromRadio.toByteArray()) + } + } +} \ No newline at end of file diff --git a/app/src/main/proto/mesh.proto b/app/src/main/proto/mesh.proto index fa259cf38..51e185b19 100644 --- a/app/src/main/proto/mesh.proto +++ b/app/src/main/proto/mesh.proto @@ -161,7 +161,12 @@ message NodeInfo { message FromRadio { oneof variant { MeshPacket packet = 1; - NodeInfo node_info = 2; + + /// Tells the phone what our node number is, can be -1 if we've not yet joined a mesh. + sint32 my_node_num = 2; + + /// One packet is sent for each node in the on radio DB + NodeInfo node_info = 3; } } @@ -177,9 +182,18 @@ message ToRadio { oneof variant { MeshPacket packet = 1; // send this packet on the mesh - RadioConfig set_radio = 2; // set the radio provisioning for this node - User set_owner = 3; // Set the owner for this node - WantNodes want_nodes = 4; // phone wants radio to send full node db to the phone + + // + // Rare operations + // + + /// phone wants radio to send full node db to the phone, This is typically the first packet sent + /// to the radio when the phone gets a bluetooth connection. + /// The radio will respond by sending back a FromRadio.my_node_num and a series of FromRadio.node_info + WantNodes want_nodes = 100; + + RadioConfig set_radio = 101; // set the radio provisioning for this node + User set_owner = 102; // Set the owner for this node } }