From 02198864c51fc05c17a40796e533aba21645932a Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 2 Mar 2021 22:12:42 +0800 Subject: [PATCH] WIP on getting new style settings/channels --- app/build.gradle | 4 +- .../java/com/geeksville/mesh/MyNodeInfo.kt | 5 +- .../geeksville/mesh/service/MeshService.kt | 129 +++++++++++++----- 3 files changed, 100 insertions(+), 38 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ffb8e7599..eac4907dd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,8 +31,8 @@ android { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) targetSdkVersion 29 - versionCode 20150 // format is Mmmss (where M is 1+the numeric major number - versionName "1.1.50" + versionCode 20200 // format is Mmmss (where M is 1+the numeric major number + versionName "1.2.00" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // per https://developer.android.com/studio/write/vector-asset-studio diff --git a/app/src/main/java/com/geeksville/mesh/MyNodeInfo.kt b/app/src/main/java/com/geeksville/mesh/MyNodeInfo.kt index 76dffcb6e..05a106cfb 100644 --- a/app/src/main/java/com/geeksville/mesh/MyNodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/MyNodeInfo.kt @@ -16,7 +16,8 @@ data class MyNodeInfo( val shouldUpdate: Boolean, // this device has old firmware val currentPacketId: Long, val messageTimeoutMsec: Int, - val minAppVersion: Int + val minAppVersion: Int, + val maxChannels: Int ) : Parcelable { /** A human readable description of the software/hardware version */ val firmwareString: String get() = "$model $region/$firmwareVersion" @@ -31,6 +32,7 @@ data class MyNodeInfo( parcel.readByte() != 0.toByte(), parcel.readLong(), parcel.readInt(), + parcel.readInt(), parcel.readInt() ) { } @@ -46,6 +48,7 @@ data class MyNodeInfo( parcel.writeLong(currentPacketId) parcel.writeInt(messageTimeoutMsec) parcel.writeInt(minAppVersion) + parcel.writeInt(maxChannels) } override fun describeContents(): Int { diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 7d8cecef8..4271b6389 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -423,7 +423,7 @@ class MeshService : Service(), Logging { private var radioConfig: RadioConfigProtos.RadioConfig? = null - private var channels = listOf() + private var channels = arrayOf() /// True after we've done our initial node db init @Volatile @@ -539,9 +539,9 @@ class MeshService : Service(), Logging { }.build() } - // FIXME, send channels to device! + TODO("Need to send channels to device") - channels = asChannels + channels = asChannels.toTypedArray() } /// Generate a new mesh packet builder with our node as the sender, and the specified node num @@ -567,7 +567,7 @@ class MeshService : Service(), Logging { */ private fun MeshProtos.MeshPacket.Builder.buildMeshPacket( wantAck: Boolean = false, - id: Int = 0, + id: Int = generatePacketId(), // always assign a packet ID if we didn't already have one hopLimit: Int = 0, priority: MeshPacket.Priority = MeshPacket.Priority.UNSET, initFn: MeshProtos.Data.Builder.() -> Unit @@ -702,6 +702,9 @@ class MeshService : Service(), Logging { // if (p.hasUser()) handleReceivedUser(fromNum, p.user) + /// We tell other apps about most message types, but some may have sensitve data, so that is not shared' + var shouldBroadcast = true + when (data.portnumValue) { Portnums.PortNum.TEXT_MESSAGE_APP_VALUE -> { debug("Received CLEAR_TEXT from $fromId") @@ -728,10 +731,20 @@ class MeshService : Service(), Logging { else handleAckNak(false, data.requestId) } + + Portnums.PortNum.ADMIN_APP_VALUE -> { + val u = AdminProtos.AdminMessage.parseFrom(data.payload) + handleReceivedAdmin(packet.from, u) + shouldBroadcast = false + } + + else -> + debug("No custom processing needed for ${data.portnumValue}") } // We always tell other apps when new data packets arrive - serviceBroadcasts.broadcastReceivedData(dataPacket) + if (shouldBroadcast) + serviceBroadcasts.broadcastReceivedData(dataPacket) GeeksvilleApplication.analytics.track( "num_data_receive", @@ -748,6 +761,35 @@ class MeshService : Service(), Logging { } } + private fun handleReceivedAdmin(fromNodeNum: Int, a: AdminProtos.AdminMessage) { + // For the time being we only care about admin messages from our local node + if (fromNodeNum == myNodeNum) { + when (a.variantCase) { + AdminProtos.AdminMessage.VariantCase.GET_RADIO_RESPONSE -> { + radioConfig = a.getRadioResponse + requestChannel(0) // Now start reading channels + } + + AdminProtos.AdminMessage.VariantCase.GET_CHANNEL_RESPONSE -> { + val mi = myNodeInfo + if (mi != null) { + val ch = a.getChannelResponse + channels[ch.index] = ch + if (ch.index + 1 < mi.maxChannels) { + // Not done yet, request next channel + requestChannel(ch.index + 1) + } else { + onHasSettings() + } + } + } + else -> + warn("No special processing needed for ${a.variantCase}") + + } + } + } + /// Update our DB of users based on someone sending out a User subpacket private fun handleReceivedUser(fromNum: Int, p: MeshProtos.User) { updateNodeInfo(fromNum) { @@ -1192,12 +1234,23 @@ class MeshService : Service(), Logging { ), currentPacketId.toLong() and 0xffffffffL, if (messageTimeoutMsec == 0) 5 * 60 * 1000 else messageTimeoutMsec, // constants from current device code - minAppVersion + minAppVersion, + maxChannels ) } newMyNodeInfo = mi + // We'll need to get a new set of channels and settings now + radioConfig = null + + // prefill the channel array with null channels + channels = Array(mi.maxChannels) { + val b = ChannelProtos.Channel.newBuilder() + b.index = it + b.build() + } + /// Track types of devices and firmware versions in use GeeksvilleApplication.analytics.setUserInfo( DataPair("region", mi.region), @@ -1272,6 +1325,18 @@ class MeshService : Service(), Logging { } } + /// If we've received our initial config, our radio settings and all of our channels, send any queueed packets and broadcast connected to clients + private fun onHasSettings() { + processEarlyPackets() // send receive any packets that were queued up + + // broadcast an intent with our new connection state + serviceBroadcasts.broadcastConnection() + onNodeDBChanged() + reportConnection() + + updateRegion() + } + private fun handleConfigComplete(configCompleteId: Int) { if (configCompleteId == configNonce) { @@ -1295,19 +1360,25 @@ class MeshService : Service(), Logging { newNodes.clear() // Just to save RAM ;-) haveNodeDB = true // we now have nodes from real hardware - processEarlyPackets() // send receive any packets that were queued up - - // broadcast an intent with our new connection state - serviceBroadcasts.broadcastConnection() - onNodeDBChanged() - reportConnection() - - updateRegion() + requestRadioConfig() + requestChannel(0) } } else warn("Ignoring stale config complete") } + private fun requestRadioConfig() { + sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket { + getRadioRequest = true + }) + } + + private fun requestChannel(channelIndex: Int) { + sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket { + getChannelRequest = channelIndex + 1 + }) + } + /** * Start the modern (REV2) API configuration flow */ @@ -1430,36 +1501,24 @@ class MeshService : Service(), Logging { throw Exception("Can't set user without a node info") // this shouldn't happen } + /// Do not use directly, instead call generatePacketId() - private var currentPacketId = 0L + private var currentPacketId = Random(System.currentTimeMillis()).nextLong().absoluteValue /** * Generate a unique packet ID (if we know enough to do so - otherwise return 0 so the device will do it) */ + @Synchronized private fun generatePacketId(): Int { + val numPacketIds = + ((1L shl 32) - 1).toLong() // A mask for only the valid packet ID bits, either 255 or maxint - myNodeInfo?.let { - val numPacketIds = - ((1L shl 32) - 1).toLong() // A mask for only the valid packet ID bits, either 255 or maxint + currentPacketId++ - if (currentPacketId == 0L) { - // We now always pick a random initial packet id (odds of collision with the device is insanely low with 32 bit ids) - val random = Random(System.currentTimeMillis()) - val devicePacketId = random.nextLong().absoluteValue + currentPacketId = currentPacketId and 0xffffffff // keep from exceeding 32 bits - // Not inited - pick a number on the opposite side of what the device is using - currentPacketId = devicePacketId + numPacketIds / 2 - } else { - currentPacketId++ - } - - currentPacketId = currentPacketId and 0xffffffff // keep from exceeding 32 bits - - // Use modulus and +1 to ensure we skip 0 on any values we return - return ((currentPacketId % numPacketIds) + 1L).toInt() - } - - return 0 // We don't have mynodeinfo yet, so just let the radio eventually assign an ID + // Use modulus and +1 to ensure we skip 0 on any values we return + return ((currentPacketId % numPacketIds) + 1L).toInt() } var firmwareUpdateFilename: UpdateFilenames? = null