From abea039922c4d5a904c7727e8f256a519d9f1fb3 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 14 Mar 2021 11:40:21 +0800 Subject: [PATCH 01/19] update libs --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5f03dba4f..47f1f9501 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -113,7 +113,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.core:core-ktx:1.3.2' - implementation 'androidx.fragment:fragment-ktx:1.3.0' + implementation 'androidx.fragment:fragment-ktx:1.3.1' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' From 483bd878ab76e6a3a066f2560dfd9f8148d20177 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 14 Mar 2021 11:42:04 +0800 Subject: [PATCH 02/19] untested change to use new hwmodel --- .../main/java/com/geeksville/mesh/NodeInfo.kt | 23 ++- .../java/com/geeksville/mesh/model/NodeDB.kt | 7 +- .../geeksville/mesh/service/MeshService.kt | 151 +++++++++++------- .../geeksville/mesh/service/MockInterface.kt | 2 +- app/src/main/proto | 2 +- 5 files changed, 119 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt index 77d26af77..bd8bacc85 100644 --- a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt @@ -14,12 +14,27 @@ import kotlinx.serialization.Serializable @Serializable @Parcelize -data class MeshUser(val id: String, val longName: String, val shortName: String) : +data class MeshUser( + val id: String, + val longName: String, + val shortName: String, + val hwModel: MeshProtos.HardwareModel +) : Parcelable { override fun toString(): String { - return "MeshUser(id=${id.anonymize}, longName=${longName.anonymize}, shortName=${shortName.anonymize})" + return "MeshUser(id=${id.anonymize}, longName=${longName.anonymize}, shortName=${shortName.anonymize}, hwModel=${hwModelString})" } + + /** a string version of the hardware model, converted into pretty lowercase and changing _ to -, and p to dot + * or null if unset + * */ + val hwModelString: String? + get() = + if (hwModel == MeshProtos.HardwareModel.UNSET) + null + else + hwModel.name.replace('_', '-').replace('p', '.').toLowerCase() } @Serializable @@ -95,8 +110,8 @@ data class NodeInfo( get() { return position?.takeIf { (it.latitude <= 90.0 && it.latitude >= -90) && // If GPS gives a crap position don't crash our app - it.latitude != 0.0 && - it.longitude != 0.0 + it.latitude != 0.0 && + it.longitude != 0.0 } } 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 d2f35f5ff..01ac4e18c 100644 --- a/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt +++ b/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt @@ -1,6 +1,7 @@ package com.geeksville.mesh.model import androidx.lifecycle.MutableLiveData +import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.MeshUser import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.Position @@ -25,7 +26,8 @@ class NodeDB(private val ui: UIViewModel) { MeshUser( "+16508765308".format(8), "Kevin MesterNoLoc", - "KLO" + "KLO", + MeshProtos.HardwareModel.ANDROID_SIM ), null ) @@ -36,7 +38,8 @@ class NodeDB(private val ui: UIViewModel) { MeshUser( "+165087653%02d".format(9 + index), "Kevin Mester$index", - "KM$index" + "KM$index", + MeshProtos.HardwareModel.ANDROID_SIM ), it ) 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 217ee0f29..a9860cbfd 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -544,7 +544,7 @@ class MeshService : Service(), Logging { debug("Sending channels to device") asChannels.forEach { - setChannel(it) + setChannel(it) } channels = asChannels.toTypedArray() @@ -727,7 +727,8 @@ class MeshService : Service(), Logging { // Handle new style routing info Portnums.PortNum.ROUTING_APP_VALUE -> { - shouldBroadcast = true // We always send acks to other apps, because they might care about the messages they sent + shouldBroadcast = + true // We always send acks to other apps, because they might care about the messages they sent val u = MeshProtos.Routing.parseFrom(data.payload) if (u.errorReasonValue == MeshProtos.Routing.Error.NONE_VALUE) handleAckNak(true, data.requestId) @@ -780,11 +781,10 @@ class MeshService : Service(), Logging { channels[ch.index] = ch debug("Admin: Received channel ${ch.index}") if (ch.index + 1 < mi.maxChannels) { - if(ch.hasSettings()) { + if (ch.hasSettings()) { // Not done yet, request next channel requestChannel(ch.index + 1) - } - else { + } else { debug("We've received the primary channel, allowing rest of app to start...") onHasSettings() } @@ -808,7 +808,8 @@ class MeshService : Service(), Logging { it.user = MeshUser( if (p.id.isNotEmpty()) p.id else oldId, // If the new update doesn't contain an ID keep our old value p.longName, - p.shortName + p.shortName, + p.hwModel ) } } @@ -1187,7 +1188,8 @@ class MeshService : Service(), Logging { MeshUser( info.user.id, info.user.longName, - info.user.shortName + info.user.shortName, + info.user.hwModel ) if (info.hasPosition()) { @@ -1214,6 +1216,79 @@ class MeshService : Service(), Logging { } + private var rawMyNodeInfo: MeshProtos.MyNodeInfo? = null + + /** Regenerate the myNodeInfo model. We call this twice. Once after we receive myNodeInfo from the device + * and again after we have the node DB (which might allow us a better notion of our HwModel. + */ + private fun regenMyNodeInfo() { + val myInfo = rawMyNodeInfo + if (myInfo != null) { + val a = RadioInterfaceService.getBondedDeviceAddress(this) + val isBluetoothInterface = a != null && a.startsWith("x") + + var hwModelStr = myInfo.hwModelDeprecated + if(hwModelStr.isEmpty()) { + try { + val ni = toNodeInfo(myNodeNum) + val asStr = ni.user?.hwModelString + if (asStr != null) + hwModelStr = asStr + } catch(_: NodeNumNotFoundException) { + warn("Can't find local node to get hardware model") + } + } + val mi = with(myInfo) { + MyNodeInfo( + myNodeNum, + hasGps, + hwModelStr, + firmwareVersion, + firmwareUpdateFilename != null, + isBluetoothInterface && com.geeksville.mesh.service.SoftwareUpdateService.shouldUpdate( + this@MeshService, + DeviceVersion(firmwareVersion) + ), + currentPacketId.toLong() and 0xffffffffL, + if (messageTimeoutMsec == 0) 5 * 60 * 1000 else messageTimeoutMsec, // constants from current device code + minAppVersion, + maxChannels + ) + } + + newMyNodeInfo = mi + setFirmwareUpdateFilename(mi) + } + } + + private fun sendAnalytics() { + val myInfo = rawMyNodeInfo + val mi = myNodeInfo + if (myInfo != null && mi != null) { + /// Track types of devices and firmware versions in use + GeeksvilleApplication.analytics.setUserInfo( + // DataPair("region", mi.region), + DataPair("firmware", mi.firmwareVersion), + DataPair("has_gps", mi.hasGPS), + DataPair("hw_model", mi.model), + DataPair("dev_error_count", myInfo.errorCount) + ) + + if (myInfo.errorCode.number != 0) { + GeeksvilleApplication.analytics.track( + "dev_error", + DataPair("code", myInfo.errorCode.number), + DataPair("address", myInfo.errorAddress), + + // We also include this info, because it is required to correctly decode address from the map file + DataPair("firmware", mi.firmwareVersion), + DataPair("hw_model", mi.model) + // DataPair("region", mi.region) + ) + } + } + } + /** * Update the nodeinfo (called from either new API version or the old one) */ @@ -1226,62 +1301,18 @@ class MeshService : Service(), Logging { ) insertPacket(packetToSave) - setFirmwareUpdateFilename(myInfo) - - val a = RadioInterfaceService.getBondedDeviceAddress(this) - val isBluetoothInterface = a != null && a.startsWith("x") - - val mi = with(myInfo) { - MyNodeInfo( - myNodeNum, - hasGps, - hwModel, - firmwareVersion, - firmwareUpdateFilename != null, - isBluetoothInterface && SoftwareUpdateService.shouldUpdate( - this@MeshService, - DeviceVersion(firmwareVersion) - ), - currentPacketId.toLong() and 0xffffffffL, - if (messageTimeoutMsec == 0) 5 * 60 * 1000 else messageTimeoutMsec, // constants from current device code - minAppVersion, - maxChannels - ) - } - - newMyNodeInfo = mi + rawMyNodeInfo = myInfo + regenMyNodeInfo() // 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) { + channels = Array(myInfo.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), - DataPair("firmware", mi.firmwareVersion), - DataPair("has_gps", mi.hasGPS), - DataPair("hw_model", mi.model), - DataPair("dev_error_count", myInfo.errorCount) - ) - - if (myInfo.errorCode.number != 0) { - GeeksvilleApplication.analytics.track( - "dev_error", - DataPair("code", myInfo.errorCode.number), - DataPair("address", myInfo.errorAddress), - - // We also include this info, because it is required to correctly decode address from the map file - DataPair("firmware", mi.firmwareVersion), - DataPair("hw_model", mi.model) - // DataPair("region", mi.region) - ) - } } @@ -1372,6 +1403,10 @@ class MeshService : Service(), Logging { newNodes.clear() // Just to save RAM ;-) haveNodeDB = true // we now have nodes from real hardware + + regenMyNodeInfo() // we have a node db now, so can possibly find a better hwmodel + sendAnalytics() + requestRadioConfig() } } else @@ -1543,12 +1578,12 @@ class MeshService : Service(), Logging { /*** * Return the filename we will install on the device */ - private fun setFirmwareUpdateFilename(info: MeshProtos.MyNodeInfo) { + private fun setFirmwareUpdateFilename(info: MyNodeInfo) { firmwareUpdateFilename = try { - if (info.region != null && info.firmwareVersion != null && info.hwModel != null) + if (info.firmwareVersion != null && info.model != null) SoftwareUpdateService.getUpdateFilename( this, - info.hwModel + info.model ) else null @@ -1660,7 +1695,7 @@ class MeshService : Service(), Logging { info("sendData dest=${p.to}, id=${p.id} <- ${p.bytes!!.size} bytes (connectionState=$connectionState)") - if(p.dataType == 0) + if (p.dataType == 0) throw Exception("Port numbers must be non-zero!") // we are now more strict // Keep a record of datapackets, so GUIs can show proper chat history 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 7c76458e5..edaa0c462 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt @@ -142,6 +142,7 @@ class MockInterface(private val service: RadioInterfaceService) : Logging, IRadi id = DataPacket.nodeNumToDefaultId(numIn) longName = "Sim " + num.toHexString() shortName = getInitials(longName) + hwModel = MeshProtos.HardwareModel.ANDROID_SIM }.build() position = MeshProtos.Position.newBuilder().apply { latitudeI = Position.degI(lat) @@ -160,7 +161,6 @@ class MockInterface(private val service: RadioInterfaceService) : Logging, IRadi MeshProtos.FromRadio.newBuilder().apply { myInfo = MeshProtos.MyNodeInfo.newBuilder().apply { myNodeNum = MY_NODE - hwModel = "Sim" messageTimeoutMsec = 5 * 60 * 1000 firmwareVersion = service.getString(R.string.cur_firmware_version) numBands = 13 diff --git a/app/src/main/proto b/app/src/main/proto index 7c025b9a4..ac26ffdc7 160000 --- a/app/src/main/proto +++ b/app/src/main/proto @@ -1 +1 @@ -Subproject commit 7c025b9a4d54bb410ec17ee653122861b413f177 +Subproject commit ac26ffdc71dad5765124186df5ec38771a0e5240 From fa17c4efe19a2cbe3945e6c49e381b0f96468424 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 15 Mar 2021 10:31:44 +0800 Subject: [PATCH 03/19] fix new hwmodel based on testing with the simulator --- .../geeksville/mesh/service/MeshService.kt | 24 +++++++++---------- .../geeksville/mesh/service/MockInterface.kt | 2 +- .../geeksville/mesh/ui/SettingsFragment.kt | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) 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 a9860cbfd..ca3e479c2 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -375,10 +375,10 @@ class MeshService : Service(), Logging { } } - private fun installNewNodeDB(newMyNodeInfo: MyNodeInfo, nodes: Array) { + private fun installNewNodeDB(ni: MyNodeInfo, nodes: Array) { discardNodeDB() // Get rid of any old state - myNodeInfo = newMyNodeInfo + myNodeInfo = ni // put our node array into our two different map representations nodeDBbyNodeNum.putAll(nodes.map { Pair(it.num, it) }) @@ -1228,15 +1228,13 @@ class MeshService : Service(), Logging { val isBluetoothInterface = a != null && a.startsWith("x") var hwModelStr = myInfo.hwModelDeprecated - if(hwModelStr.isEmpty()) { - try { - val ni = toNodeInfo(myNodeNum) - val asStr = ni.user?.hwModelString - if (asStr != null) - hwModelStr = asStr - } catch(_: NodeNumNotFoundException) { - warn("Can't find local node to get hardware model") - } + if (hwModelStr.isEmpty()) { + val nodeNum = + myInfo.myNodeNum // Note: can't use the normal property because myNodeInfo not yet setup + val ni = nodeDBbyNodeNum[nodeNum] // can't use toNodeInfo because too early + val asStr = ni?.user?.hwModelString + if (asStr != null) + hwModelStr = asStr } val mi = with(myInfo) { MyNodeInfo( @@ -1397,7 +1395,7 @@ class MeshService : Service(), Logging { else { discardNodeDB() debug("Installing new node DB") - myNodeInfo = newMyNodeInfo + myNodeInfo = newMyNodeInfo// Install myNodeInfo as current newNodes.forEach(::installNodeInfo) newNodes.clear() // Just to save RAM ;-) @@ -1405,6 +1403,8 @@ class MeshService : Service(), Logging { haveNodeDB = true // we now have nodes from real hardware regenMyNodeInfo() // we have a node db now, so can possibly find a better hwmodel + myNodeInfo = newMyNodeInfo // we might have just updated myNodeInfo + sendAnalytics() requestRadioConfig() 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 edaa0c462..80d4495be 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt @@ -162,7 +162,7 @@ class MockInterface(private val service: RadioInterfaceService) : Logging, IRadi myInfo = MeshProtos.MyNodeInfo.newBuilder().apply { myNodeNum = MY_NODE messageTimeoutMsec = 5 * 60 * 1000 - firmwareVersion = service.getString(R.string.cur_firmware_version) + firmwareVersion = "1.2.8" // Pretend to be running an older 1.2 version numBands = 13 maxChannels = 8 }.build() 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 46b780ecf..fde6466bd 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -610,7 +610,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { statusText.text = getString(R.string.must_set_region) connected == MeshService.ConnectionState.CONNECTED -> { - val fwStr = info?.firmwareString ?: "" + val fwStr = info?.firmwareString ?: "unknown" statusText.text = getString(R.string.connected_to).format(fwStr) } connected == MeshService.ConnectionState.DISCONNECTED -> From c8107441dec78c5187de8c6ba085ba0eb9be6227 Mon Sep 17 00:00:00 2001 From: goga Date: Mon, 15 Mar 2021 23:46:53 +0500 Subject: [PATCH 04/19] display timeAgo properly and show coords in users screen --- .../com/geeksville/mesh/ui/UsersFragment.kt | 36 ++++++------------- .../main/res/layout/adapter_node_layout.xml | 13 +++++++ app/src/main/res/values/strings.xml | 1 + 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index 0b6529679..60116a948 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -17,6 +17,7 @@ import com.geeksville.mesh.R import com.geeksville.mesh.databinding.AdapterNodeLayoutBinding import com.geeksville.mesh.databinding.NodelistFragmentBinding import com.geeksville.mesh.model.UIViewModel +import com.geeksville.util.formatAgo import java.text.ParseException import java.util.* @@ -24,6 +25,7 @@ import java.util.* class UsersFragment : ScreenFragment("Users"), Logging { private var _binding: NodelistFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! @@ -34,6 +36,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { class ViewHolder(itemView: AdapterNodeLayoutBinding) : RecyclerView.ViewHolder(itemView.root) { val nodeNameView = itemView.nodeNameView val distanceView = itemView.distanceView + val coordsview = itemView.coordsView val batteryPctView = itemView.batteryPercentageView val lastTime = itemView.lastConnectionView val powerIcon = itemView.batteryIcon @@ -108,6 +111,13 @@ class UsersFragment : ScreenFragment("Users"), Logging { holder.nodeNameView.text = n.user?.longName ?: n.user?.id ?: "Unknown node" val ourNodeInfo = model.nodeDB.ourNodeInfo + val pos = ourNodeInfo?.position; + if (pos != null) { + holder.coordsview.text = pos.latitude.toString() + " " + pos.longitude + holder.coordsview.visibility = View.VISIBLE + } else { + holder.coordsview.visibility = View.INVISIBLE + } val distance = ourNodeInfo?.distanceStr(n) if (distance != null) { holder.distanceView.text = distance @@ -118,7 +128,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { renderBattery(n.batteryPctLevel, holder) - holder.lastTime.text = getLastTimeValue(n) + holder.lastTime.text = formatAgo(n.lastSeen); } private var nodes = arrayOf() @@ -150,30 +160,6 @@ class UsersFragment : ScreenFragment("Users"), Logging { }) } - private fun getLastTimeValue(n: NodeInfo): String { - var lastTimeText = "?" - val currentTime = (System.currentTimeMillis()/1000).toInt() - val threeDaysLong = 3 * 60*60*24 - - //if the lastSeen is too old - if (n.lastSeen < (currentTime - threeDaysLong)) - return lastTimeText - - try { - val toLong: Long = n.lastSeen.toLong() - val long1000 = toLong * 1000L - val date = Date(long1000) - val timeFormat = DateFormat.getTimeFormat(context) - - lastTimeText = timeFormat.format(date) - - } catch (e: ParseException) { - // - } - return lastTimeText - } - - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? diff --git a/app/src/main/res/layout/adapter_node_layout.xml b/app/src/main/res/layout/adapter_node_layout.xml index 86d70f043..def23200e 100644 --- a/app/src/main/res/layout/adapter_node_layout.xml +++ b/app/src/main/res/layout/adapter_node_layout.xml @@ -51,6 +51,19 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/imageView" /> + + Okay You must set a region! Region + 55.332244 34.442211 From 53745ca9f58685ee0df92289013fec246d11d3e3 Mon Sep 17 00:00:00 2001 From: goga Date: Tue, 16 Mar 2021 00:05:28 +0500 Subject: [PATCH 05/19] display timeAgo properly and show coords in users screen --- app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt | 3 ++- app/src/main/proto | 2 +- design | 2 +- geeksville-androidlib | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt index e4d6bd771..67a1a8583 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -13,6 +13,7 @@ import com.geeksville.android.Logging import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.R import com.geeksville.mesh.model.UIViewModel +import com.geeksville.util.formatAgo import com.mapbox.geojson.Feature import com.mapbox.geojson.FeatureCollection import com.mapbox.geojson.Point @@ -79,7 +80,7 @@ class MapFragment : ScreenFragment("Map"), Logging { ) ) node.user?.let { - f.addStringProperty("name", it.longName) + f.addStringProperty("name", it.longName + " " + formatAgo(node.lastSeen)) } f } diff --git a/app/src/main/proto b/app/src/main/proto index 7c025b9a4..ac26ffdc7 160000 --- a/app/src/main/proto +++ b/app/src/main/proto @@ -1 +1 @@ -Subproject commit 7c025b9a4d54bb410ec17ee653122861b413f177 +Subproject commit ac26ffdc71dad5765124186df5ec38771a0e5240 diff --git a/design b/design index a81074152..d0339f029 160000 --- a/design +++ b/design @@ -1 +1 @@ -Subproject commit a81074152157fa54b0d02ccbbd6a6357cc3cedcf +Subproject commit d0339f0297c629f1bd6873b4abccfecb98443538 diff --git a/geeksville-androidlib b/geeksville-androidlib index 99cf0da30..6da250358 160000 --- a/geeksville-androidlib +++ b/geeksville-androidlib @@ -1 +1 @@ -Subproject commit 99cf0da30fe41163a735ac291f3dd018a7d6295d +Subproject commit 6da250358ed13e3c58fd4fa2a123b01b3826d4bf From 452bf6f6cb6ee11f12baee660bd43aa4ed8143c2 Mon Sep 17 00:00:00 2001 From: goga Date: Tue, 16 Mar 2021 10:04:01 +0500 Subject: [PATCH 06/19] show coordinates with geo url to open in other mapping app --- .../geeksville/mesh/service/MeshService.kt | 6 +++--- .../geeksville/mesh/service/MockInterface.kt | 2 +- .../com/geeksville/mesh/ui/MapFragment.kt | 12 ++++++++---- .../com/geeksville/mesh/ui/UsersFragment.kt | 19 +++++++++++-------- .../main/res/layout/adapter_node_layout.xml | 3 ++- 5 files changed, 25 insertions(+), 17 deletions(-) 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 217ee0f29..f40b38115 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1235,7 +1235,7 @@ class MeshService : Service(), Logging { MyNodeInfo( myNodeNum, hasGps, - hwModel, + hwModelDeprecated, firmwareVersion, firmwareUpdateFilename != null, isBluetoothInterface && SoftwareUpdateService.shouldUpdate( @@ -1545,10 +1545,10 @@ class MeshService : Service(), Logging { */ private fun setFirmwareUpdateFilename(info: MeshProtos.MyNodeInfo) { firmwareUpdateFilename = try { - if (info.region != null && info.firmwareVersion != null && info.hwModel != null) + if (info.region != null && info.firmwareVersion != null && info.hwModelDeprecated != null) SoftwareUpdateService.getUpdateFilename( this, - info.hwModel + info.hwModelDeprecated ) else null 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 7c76458e5..a6786c13c 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt @@ -160,7 +160,7 @@ class MockInterface(private val service: RadioInterfaceService) : Logging, IRadi MeshProtos.FromRadio.newBuilder().apply { myInfo = MeshProtos.MyNodeInfo.newBuilder().apply { myNodeNum = MY_NODE - hwModel = "Sim" + hwModelDeprecated = "Sim" messageTimeoutMsec = 5 * 60 * 1000 firmwareVersion = service.getString(R.string.cur_firmware_version) numBands = 13 diff --git a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt index 67a1a8583..f40c78b3d 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -80,7 +80,7 @@ class MapFragment : ScreenFragment("Map"), Logging { ) ) node.user?.let { - f.addStringProperty("name", it.longName + " " + formatAgo(node.lastSeen)) + f.addStringProperty("name", it.longName + " " + formatAgo(p.time)) } f } @@ -94,7 +94,8 @@ class MapFragment : ScreenFragment("Map"), Logging { } fun zoomToNodes(map: MapboxMap) { - val nodesWithPosition = model.nodeDB.nodes.value?.values?.filter { it.validPosition != null } + val nodesWithPosition = + model.nodeDB.nodes.value?.values?.filter { it.validPosition != null } if (nodesWithPosition != null && nodesWithPosition.isNotEmpty()) { val update = if (nodesWithPosition.size >= 2) { // Multiple nodes, make them all fit on the map view @@ -159,7 +160,10 @@ class MapFragment : ScreenFragment("Map"), Logging { if (view != null) { // it might have gone away by now // val markerIcon = BitmapFactory.decodeResource(context.resources, R.drawable.ic_twotone_person_pin_24) val markerIcon = - ContextCompat.getDrawable(requireActivity(), R.drawable.ic_twotone_person_pin_24)!! + ContextCompat.getDrawable( + requireActivity(), + R.drawable.ic_twotone_person_pin_24 + )!! map.setStyle(Style.OUTDOORS) { style -> style.addSource(nodePositions) @@ -177,7 +181,7 @@ class MapFragment : ScreenFragment("Map"), Logging { // Any times nodes change update our map model.nodeDB.nodes.observe(viewLifecycleOwner, Observer { nodes -> - if(isViewVisible) + if (isViewVisible) onNodesChanged(map, nodes.values) }) zoomToNodes(map) diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index 60116a948..0eed4d7a2 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -2,11 +2,13 @@ package com.geeksville.mesh.ui import android.os.Bundle -import android.text.format.DateFormat +import android.text.Html +import android.text.method.LinkMovementMethod import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat +import androidx.core.text.HtmlCompat import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager @@ -18,8 +20,6 @@ import com.geeksville.mesh.databinding.AdapterNodeLayoutBinding import com.geeksville.mesh.databinding.NodelistFragmentBinding import com.geeksville.mesh.model.UIViewModel import com.geeksville.util.formatAgo -import java.text.ParseException -import java.util.* class UsersFragment : ScreenFragment("Users"), Logging { @@ -36,7 +36,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { class ViewHolder(itemView: AdapterNodeLayoutBinding) : RecyclerView.ViewHolder(itemView.root) { val nodeNameView = itemView.nodeNameView val distanceView = itemView.distanceView - val coordsview = itemView.coordsView + val coordsView = itemView.coordsView val batteryPctView = itemView.batteryPercentageView val lastTime = itemView.lastConnectionView val powerIcon = itemView.batteryIcon @@ -111,12 +111,15 @@ class UsersFragment : ScreenFragment("Users"), Logging { holder.nodeNameView.text = n.user?.longName ?: n.user?.id ?: "Unknown node" val ourNodeInfo = model.nodeDB.ourNodeInfo - val pos = ourNodeInfo?.position; + val pos = ourNodeInfo?.validPosition; if (pos != null) { - holder.coordsview.text = pos.latitude.toString() + " " + pos.longitude - holder.coordsview.visibility = View.VISIBLE + val html = + "${pos.latitude.toString()} ${pos.longitude}" + holder.coordsView.text = HtmlCompat.fromHtml(html, Html.FROM_HTML_MODE_LEGACY) + holder.coordsView.movementMethod = LinkMovementMethod.getInstance() + holder.coordsView.visibility = View.VISIBLE } else { - holder.coordsview.visibility = View.INVISIBLE + holder.coordsView.visibility = View.INVISIBLE } val distance = ourNodeInfo?.distanceStr(n) if (distance != null) { diff --git a/app/src/main/res/layout/adapter_node_layout.xml b/app/src/main/res/layout/adapter_node_layout.xml index def23200e..e2b5fbe0a 100644 --- a/app/src/main/res/layout/adapter_node_layout.xml +++ b/app/src/main/res/layout/adapter_node_layout.xml @@ -55,10 +55,11 @@ android:id="@+id/coords_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="76dp" + android:layout_marginStart="140dp" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:text="@string/sample_coords" + android:textAppearance="@style/TextAppearance.AppCompat.Small" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/imageView" From 6958e3c86a1b05d2d3a51531c0fa07b26e8170b8 Mon Sep 17 00:00:00 2001 From: goga Date: Tue, 16 Mar 2021 11:38:42 +0500 Subject: [PATCH 07/19] coords position fix --- app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt | 3 ++- app/src/main/res/layout/adapter_node_layout.xml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index 0eed4d7a2..f5f743c14 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -113,8 +113,9 @@ class UsersFragment : ScreenFragment("Users"), Logging { val ourNodeInfo = model.nodeDB.ourNodeInfo val pos = ourNodeInfo?.validPosition; if (pos != null) { + val coords = String.format("%.5f %.5f", pos.latitude, pos.longitude).replace(",",".") val html = - "${pos.latitude.toString()} ${pos.longitude}" + "${coords}" holder.coordsView.text = HtmlCompat.fromHtml(html, Html.FROM_HTML_MODE_LEGACY) holder.coordsView.movementMethod = LinkMovementMethod.getInstance() holder.coordsView.visibility = View.VISIBLE diff --git a/app/src/main/res/layout/adapter_node_layout.xml b/app/src/main/res/layout/adapter_node_layout.xml index e2b5fbe0a..f5577aaac 100644 --- a/app/src/main/res/layout/adapter_node_layout.xml +++ b/app/src/main/res/layout/adapter_node_layout.xml @@ -55,13 +55,13 @@ android:id="@+id/coords_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="140dp" + android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:text="@string/sample_coords" android:textAppearance="@style/TextAppearance.AppCompat.Small" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" + app:layout_constraintStart_toEndOf="@+id/distance_view" app:layout_constraintTop_toBottomOf="@+id/imageView" app:layout_constraintVertical_bias="0.0" /> From 98356b9205a4dcdbac086bbcc8d32576cccb842d Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 17 Mar 2021 15:37:09 +0800 Subject: [PATCH 08/19] fix autobug crash when primary channel is null --- README.md | 2 +- .../java/com/geeksville/mesh/MainActivity.kt | 78 ++++++++++--------- .../com/geeksville/mesh/model/ChannelSet.kt | 8 +- .../com/geeksville/mesh/ui/ChannelFragment.kt | 12 ++- app/src/main/res/values/strings.xml | 1 + 5 files changed, 53 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index e7355a6ed..bb4040be5 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The app is also distributed for Amazon Fire devices via the Amazon appstore: [![ If you would like to develop this application we'd love your help! These build instructions are brief and should be improved, please send a PR if you can. -* Use Android Studio 4.0 RC 1 to build/debug (other versions might work but no promises) +* Use Android Studio 4.1.2 to build/debug (other versions might work but no promises) * Use "git submodule update --init --recursive" to pull in the various submodules we depend on * There are a few config files which you'll need to copy from templates included in the project. Run the following commands to do so: diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 136169f67..4791427b4 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -20,9 +20,7 @@ import android.os.Build import android.os.Bundle import android.os.Handler import android.os.RemoteException -import android.text.SpannableString import android.text.method.LinkMovementMethod -import android.text.util.Linkify import android.view.Menu import android.view.MenuItem import android.view.MotionEvent @@ -44,7 +42,6 @@ import com.geeksville.android.Logging import com.geeksville.android.ServiceClient import com.geeksville.concurrent.handledLaunch import com.geeksville.mesh.databinding.ActivityMainBinding -import com.geeksville.mesh.model.Channel import com.geeksville.mesh.model.ChannelSet import com.geeksville.mesh.model.DeviceVersion import com.geeksville.mesh.model.UIViewModel @@ -307,11 +304,7 @@ class MainActivity : AppCompatActivity(), Logging, if (deniedPermissions.isNotEmpty()) { errormsg("Denied permissions: ${deniedPermissions.joinToString(",")}") - Toast.makeText( - this, - getString(R.string.permission_missing), - Toast.LENGTH_LONG - ).show() + showToast(R.string.permission_missing) } } @@ -659,7 +652,7 @@ class MainActivity : AppCompatActivity(), Logging, val curVer = DeviceVersion(info.firmwareVersion ?: "0.0.0") val minVer = DeviceVersion("1.2.0") - if(curVer < minVer) + if (curVer < minVer) showAlert(R.string.firmware_too_old, R.string.firmware_old) else { // If our app is too old/new, we probably don't understand the new radioconfig messages, so we don't read them until here @@ -687,40 +680,52 @@ class MainActivity : AppCompatActivity(), Logging, } } + private fun showToast(msgId: Int) { + Toast.makeText( + this, + msgId, + Toast.LENGTH_LONG + ).show() + } + + private fun showToast(msg: String) { + Toast.makeText( + this, + msg, + Toast.LENGTH_LONG + ).show() + } + private fun perhapsChangeChannel() { // If the is opening a channel URL, handle it now requestedChannelUrl?.let { url -> try { val channels = ChannelSet(url) val primary = channels.primaryChannel - requestedChannelUrl = null + if (primary == null) + showToast(R.string.channel_invalid) + else { + requestedChannelUrl = null - MaterialAlertDialogBuilder(this) - .setTitle(R.string.new_channel_rcvd) - .setMessage(getString(R.string.do_you_want_switch).format(primary.name)) - .setNeutralButton(R.string.cancel) { _, _ -> - // Do nothing - } - .setPositiveButton(R.string.accept) { _, _ -> - debug("Setting channel from URL") - try { - model.setChannels(channels) - } catch (ex: RemoteException) { - errormsg("Couldn't change channel ${ex.message}") - Toast.makeText( - this, - "Couldn't change channel, because radio is not yet connected. Please try again.", - Toast.LENGTH_SHORT - ).show() + MaterialAlertDialogBuilder(this) + .setTitle(R.string.new_channel_rcvd) + .setMessage(getString(R.string.do_you_want_switch).format(primary.name)) + .setNeutralButton(R.string.cancel) { _, _ -> + // Do nothing } - } - .show() + .setPositiveButton(R.string.accept) { _, _ -> + debug("Setting channel from URL") + try { + model.setChannels(channels) + } catch (ex: RemoteException) { + errormsg("Couldn't change channel ${ex.message}") + showToast(R.string.cant_change_no_radio) + } + } + .show() + } } catch (ex: InvalidProtocolBufferException) { - Toast.makeText( - this, - R.string.channel_invalid, - Toast.LENGTH_LONG - ).show() + showToast(R.string.channel_invalid) } } } @@ -869,8 +874,7 @@ class MainActivity : AppCompatActivity(), Logging, errormsg("Device error during init ${ex.message}") model.isConnected.value = MeshService.ConnectionState.valueOf(service.connectionState()) - } - finally { + } finally { connectionJob = null } @@ -1037,7 +1041,7 @@ class MainActivity : AppCompatActivity(), Logging, try { val packageInfo: PackageInfo = packageManager.getPackageInfo(packageName, 0) val versionName = packageInfo.versionName - Toast.makeText(applicationContext, versionName, Toast.LENGTH_LONG).show() + showToast(versionName) } catch (e: PackageManager.NameNotFoundException) { errormsg("Can not find the version: ${e.message}") } diff --git a/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt b/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt index d6b0bcf5e..59269184a 100644 --- a/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt +++ b/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt @@ -47,9 +47,11 @@ data class ChannelSet( /** * Return the primary channel info */ - val primaryChannel: Channel get() { - return Channel(protobuf.getSettings(0)) - } + val primaryChannel: Channel? get() = + if(protobuf.settingsCount > 0) + Channel(protobuf.getSettings(0)) + else + null /// Return an URL that represents the current channel values /// @param upperCasePrefix - portions of the URL can be upper case to make for more efficient QR codes diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt index d4c5e8303..bc32450c5 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -79,11 +79,10 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { /// Pull the latest data from the model (discarding any user edits) private fun setGUIfromModel() { val channels = model.channels.value + val channel = channels?.primaryChannel binding.editableCheckbox.isChecked = false // start locked - if (channels != null) { - val channel = channels.primaryChannel - + if (channel != null) { binding.qrView.visibility = View.VISIBLE binding.channelNameEdit.visibility = View.VISIBLE binding.channelNameEdit.setText(channel.humanName) @@ -156,8 +155,8 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { val checked = binding.editableCheckbox.isChecked if (checked) { // User just unlocked for editing - remove the # goo around the channel name - model.channels.value?.let { channels -> - binding.channelNameEdit.setText(channels.primaryChannel.name) + model.channels.value?.primaryChannel?.let { ch -> + binding.channelNameEdit.setText(ch.name) } } else { // User just locked it, we should warn and then apply changes to radio @@ -169,8 +168,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { } .setPositiveButton(getString(R.string.accept)) { _, _ -> // Generate a new channel with only the changes the user can change in the GUI - model.channels.value?.let { old -> - val oldPrimary = old.primaryChannel + model.channels.value?.primaryChannel?.let { oldPrimary -> val newSettings = oldPrimary.settings.toBuilder() newSettings.name = binding.channelNameEdit.text.toString().trim() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b953889e2..c0f9644ea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -94,4 +94,5 @@ Okay You must set a region! Region + Couldn\'t change channel, because radio is not yet connected. Please try again. From c2f578c97160c4ed6cf1b5bf4faaf319845fae4f Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 17 Mar 2021 15:40:09 +0800 Subject: [PATCH 09/19] fix autobug when setting channels on !connected radio --- app/src/main/java/com/geeksville/mesh/service/MeshService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ca3e479c2..3c667e2ec 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1751,7 +1751,7 @@ class MeshService : Service(), Logging { channelSet.toByteArray() } - override fun setChannels(payload: ByteArray?) { + override fun setChannels(payload: ByteArray?) = toRemoteExceptions { val parsed = AppOnlyProtos.ChannelSet.parseFrom(payload) channelSet = parsed } From 3175d5e2d2d2e7da1f1324b94b4f933374f5e5a4 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 17 Mar 2021 15:53:08 +0800 Subject: [PATCH 10/19] autobug while app shutting down --- app/src/main/java/com/geeksville/mesh/MainActivity.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 4791427b4..348d67b56 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -37,6 +37,7 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.lifecycle.Observer import androidx.viewpager2.adapter.FragmentStateAdapter +import com.geeksville.android.BindFailedException import com.geeksville.android.GeeksvilleApplication import com.geeksville.android.Logging import com.geeksville.android.ServiceClient @@ -950,8 +951,14 @@ class MainActivity : AppCompatActivity(), Logging, } } - bindMeshService() - + try { + bindMeshService() + } + catch(ex: BindFailedException) { + // App is probably shutting down, ignore + errormsg("Bind of MeshService failed") + } + val bonded = RadioInterfaceService.getBondedDeviceAddress(this) != null if (!bonded && usbDevice == null) // we will handle USB later showSettingsPage() From aac5e7cd7ee8bb7a47e1cd01f17c9a5f00f79be3 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 17 Mar 2021 15:53:36 +0800 Subject: [PATCH 11/19] stop fetching channels at first !disabled entry --- .../main/java/com/geeksville/mesh/service/MeshService.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 3c667e2ec..e429c66ac 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -781,11 +781,13 @@ class MeshService : Service(), Logging { channels[ch.index] = ch debug("Admin: Received channel ${ch.index}") if (ch.index + 1 < mi.maxChannels) { - if (ch.hasSettings()) { + + // Stop once we get to the first disabled entry + if (/* ch.hasSettings() || */ ch.role != ChannelProtos.Channel.Role.DISABLED) { // Not done yet, request next channel requestChannel(ch.index + 1) } else { - debug("We've received the primary channel, allowing rest of app to start...") + debug("We've received the last channel, allowing rest of app to start...") onHasSettings() } } else { From 6ed7af37638e1e586a45d2fde374898d26047e1f Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 17 Mar 2021 16:04:29 +0800 Subject: [PATCH 12/19] fix autobug with getnodenum failing during startup --- .../service/MeshServiceLocationCallback.kt | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshServiceLocationCallback.kt b/app/src/main/java/com/geeksville/mesh/service/MeshServiceLocationCallback.kt index b0bc2e857..ac04a0f24 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceLocationCallback.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceLocationCallback.kt @@ -38,12 +38,22 @@ class MeshServiceLocationCallback( MeshService.info("got phone location") if (location.isAccurateForMesh) { // if within 200 meters, or accuracy is unknown - // Do we want to broadcast this position globally, or are we just telling the local node what its current position is ( - val shouldBroadcast = isAllowedToSend() - val destinationNumber = if (shouldBroadcast) DataPacket.NODENUM_BROADCAST else getNodeNum() + try { + // Do we want to broadcast this position globally, or are we just telling the local node what its current position is ( + val shouldBroadcast = isAllowedToSend() + val destinationNumber = + if (shouldBroadcast) DataPacket.NODENUM_BROADCAST else getNodeNum() - // Note: we never want this message sent as a reliable message, because it is low value and we'll be sending one again later anyways - sendPosition(location, destinationNumber, wantResponse = false) + // Note: we never want this message sent as a reliable message, because it is low value and we'll be sending one again later anyways + sendPosition(location, destinationNumber, wantResponse = false) + + } catch (ex: RemoteException) { // Really a RadioNotConnected exception, but it has changed into this type via remoting + MeshService.warn("Lost connection to radio, stopping location requests") + onSendPositionFailed() + } catch (ex: BLEException) { // Really a RadioNotConnected exception, but it has changed into this type via remoting + MeshService.warn("BLE exception, stopping location requests $ex") + onSendPositionFailed() + } } else { MeshService.warn("accuracy ${location.accuracy} is too poor to use") } @@ -51,21 +61,13 @@ class MeshServiceLocationCallback( } private fun sendPosition(location: Location, destinationNumber: Int, wantResponse: Boolean) { - try { - onSendPosition( - location.latitude, - location.longitude, - location.altitude.toInt(), - destinationNumber, - wantResponse // wantResponse? - ) - } catch (ex: RemoteException) { // Really a RadioNotConnected exception, but it has changed into this type via remoting - MeshService.warn("Lost connection to radio, stopping location requests") - onSendPositionFailed() - } catch (ex: BLEException) { // Really a RadioNotConnected exception, but it has changed into this type via remoting - MeshService.warn("BLE exception, stopping location requests $ex") - onSendPositionFailed() - } + onSendPosition( + location.latitude, + location.longitude, + location.altitude.toInt(), + destinationNumber, + wantResponse // wantResponse? + ) } /** From 3e3dc47440ce9006eaa11f7f80c2d56fab42dfea Mon Sep 17 00:00:00 2001 From: goga Date: Thu, 18 Mar 2021 11:11:37 +0500 Subject: [PATCH 13/19] oops, show not only my coords, but neighbour's too --- .../com/geeksville/mesh/ui/UsersFragment.kt | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index f5f743c14..66bc2c270 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -20,6 +20,7 @@ import com.geeksville.mesh.databinding.AdapterNodeLayoutBinding import com.geeksville.mesh.databinding.NodelistFragmentBinding import com.geeksville.mesh.model.UIViewModel import com.geeksville.util.formatAgo +import java.net.URLEncoder class UsersFragment : ScreenFragment("Users"), Logging { @@ -107,21 +108,28 @@ class UsersFragment : ScreenFragment("Users"), Logging { */ override fun onBindViewHolder(holder: ViewHolder, position: Int) { val n = nodes[position] + val name = n.user?.longName ?: n.user?.id ?: "Unknown node" + holder.nodeNameView.text = name - holder.nodeNameView.text = n.user?.longName ?: n.user?.id ?: "Unknown node" - - val ourNodeInfo = model.nodeDB.ourNodeInfo - val pos = ourNodeInfo?.validPosition; + val pos = n.validPosition; if (pos != null) { - val coords = String.format("%.5f %.5f", pos.latitude, pos.longitude).replace(",",".") + val coords = + String.format("%.5f %.5f", pos.latitude, pos.longitude).replace(",", ".") val html = - "${coords}" + "${coords}" holder.coordsView.text = HtmlCompat.fromHtml(html, Html.FROM_HTML_MODE_LEGACY) holder.coordsView.movementMethod = LinkMovementMethod.getInstance() holder.coordsView.visibility = View.VISIBLE } else { holder.coordsView.visibility = View.INVISIBLE } + + val ourNodeInfo = model.nodeDB.ourNodeInfo val distance = ourNodeInfo?.distanceStr(n) if (distance != null) { holder.distanceView.text = distance From c405cdc2001646412906b42457cbdf67f60370a3 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 19 Mar 2021 17:09:35 +0800 Subject: [PATCH 14/19] try to notice when user wants to go back to default settings --- .../main/java/com/geeksville/mesh/model/Channel.kt | 10 +++------- .../java/com/geeksville/mesh/model/ChannelSet.kt | 5 ----- .../java/com/geeksville/mesh/ui/ChannelFragment.kt | 12 +++++------- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/model/Channel.kt b/app/src/main/java/com/geeksville/mesh/model/Channel.kt index 5686bb209..a2af3e92b 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Channel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Channel.kt @@ -19,19 +19,15 @@ data class Channel( val settings: ChannelProtos.ChannelSettings = ChannelProtos.ChannelSettings.getDefaultInstance() ) { companion object { - // Note: this string _SHOULD NOT BE LOCALIZED_ because it directly hashes to values used on the device for the default channel name. - // FIXME - make this work with new channel name system - const val defaultChannelName = "Default" - // These bytes must match the well known and not secret bytes used the default channel AES128 key device code val channelDefaultKey = byteArrayOfInts( 0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf ) - // Placeholder when emulating - val emulated = Channel( - ChannelProtos.ChannelSettings.newBuilder().setName(defaultChannelName) + // TH=he unsecured channel that devices ship with + val defaultChannel = Channel( + ChannelProtos.ChannelSettings.newBuilder() .setModemConfig(ChannelProtos.ChannelSettings.ModemConfig.Bw125Cr45Sf128).build() ) } diff --git a/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt b/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt index 59269184a..3fa0b4697 100644 --- a/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt +++ b/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt @@ -16,11 +16,6 @@ data class ChannelSet( ) { companion object { - // Placeholder when emulating - val emulated = ChannelSet( - AppOnlyProtos.ChannelSet.newBuilder().addSettings(Channel.emulated.settings).build() - ) - const val prefix = "https://www.meshtastic.org/d/#" private const val base64Flags = Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt index bc32450c5..9ca623088 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -169,12 +169,12 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { .setPositiveButton(getString(R.string.accept)) { _, _ -> // Generate a new channel with only the changes the user can change in the GUI model.channels.value?.primaryChannel?.let { oldPrimary -> - val newSettings = oldPrimary.settings.toBuilder() + var newSettings = oldPrimary.settings.toBuilder() newSettings.name = binding.channelNameEdit.text.toString().trim() - // Generate a new AES256 key (for any channel not named Default) + // Generate a new AES256 key unleess the user is trying to go back to stock if (!newSettings.name.equals( - Channel.defaultChannelName, + Channel.defaultChannel.name, ignoreCase = true ) ) { @@ -184,10 +184,8 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { random.nextBytes(bytes) newSettings.psk = ByteString.copyFrom(bytes) } else { - debug("ASSIGNING NEW default AES128 KEY") - newSettings.name = - Channel.defaultChannelName // Fix any case errors - newSettings.psk = ByteString.copyFrom(Channel.channelDefaultKey) + debug("Switching back to default channel") + newSettings = Channel.defaultChannel.settings.toBuilder() } val selectedChannelOptionString = From 699d5076b5425b6c90bf108e695539da39096a90 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 19 Mar 2021 17:42:26 +0800 Subject: [PATCH 15/19] use unmock to test and fix the channel suffix generation to match device --- app/build.gradle | 6 ++ .../java/com/geeksville/mesh/model/Channel.kt | 21 ++----- .../java/com/geeksville/mesh/NodeInfoTest.kt | 7 ++- .../geeksville/mesh/model/ChannelSetTest.kt | 16 +++++ .../mesh/service/MeshServiceTest.kt | 63 ++++++++++--------- build.gradle | 5 +- 6 files changed, 68 insertions(+), 50 deletions(-) create mode 100644 app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt diff --git a/app/build.gradle b/app/build.gradle index 47f1f9501..527d63567 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,6 +4,7 @@ apply plugin: 'kotlin-parcelize' apply plugin: 'kotlinx-serialization' apply plugin: 'com.google.gms.google-services' apply plugin: 'com.github.triplet.play' +apply plugin: 'de.mobilej.unmock' // apply plugin: "app.brant.amazonappstorepublisher" // Apply the Crashlytics Gradle plugin @@ -14,6 +15,11 @@ apply plugin: 'com.google.protobuf' apply plugin: 'kotlin-kapt' +unMock { + keep "android.net.Uri" + keep "android.util.Base64" +} + android { /* signingConfigs { diff --git a/app/src/main/java/com/geeksville/mesh/model/Channel.kt b/app/src/main/java/com/geeksville/mesh/model/Channel.kt index a2af3e92b..665a8de25 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Channel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Channel.kt @@ -1,15 +1,7 @@ package com.geeksville.mesh.model -import android.graphics.Bitmap -import android.net.Uri -import android.util.Base64 import com.geeksville.mesh.ChannelProtos -import com.geeksville.mesh.MeshProtos import com.google.protobuf.ByteString -import com.google.zxing.BarcodeFormat -import com.google.zxing.MultiFormatWriter -import com.journeyapps.barcodescanner.BarcodeEncoder -import java.net.MalformedURLException /** Utility function to make it easy to declare byte arrays - FIXME move someplace better */ fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() } @@ -74,14 +66,13 @@ data class Channel( */ val humanName: String get() { - val suffix: Char = if (settings.psk.size() != 1) { - // we have a full PSK, so hash it to generate the suffix - val code = settings.psk.fold(0, { acc, x -> acc xor (x.toInt() and 0xff) }) - - 'A' + (code % 26) - } else - '0' + settings.psk.byteAt(0).toInt() + // start with the PSK then xor in the name + val pskCode = xorHash(psk.toByteArray()) + val nameCode = xorHash(name.toByteArray()) + val suffix = 'A' + ((pskCode xor nameCode) % 26) return "#${name}-${suffix}" } } + +fun xorHash(b: ByteArray) = b.fold(0, { acc, x -> acc xor (x.toInt() and 0xff) }) \ No newline at end of file diff --git a/app/src/test/java/com/geeksville/mesh/NodeInfoTest.kt b/app/src/test/java/com/geeksville/mesh/NodeInfoTest.kt index aa9059e57..cf6713cc9 100644 --- a/app/src/test/java/com/geeksville/mesh/NodeInfoTest.kt +++ b/app/src/test/java/com/geeksville/mesh/NodeInfoTest.kt @@ -8,9 +8,10 @@ import org.junit.Test import java.util.* class NodeInfoTest { - val ni1 = NodeInfo(4, MeshUser("+one", "User One", "U1"), Position(37.1, 121.1, 35)) - val ni2 = NodeInfo(5, MeshUser("+two", "User Two", "U2"), Position(37.11, 121.1, 40)) - val ni3 = NodeInfo(6, MeshUser("+three", "User Three", "U3"), Position(37.101, 121.1, 40)) + val model = MeshProtos.HardwareModel.ANDROID_SIM + val ni1 = NodeInfo(4, MeshUser("+one", "User One", "U1", model), Position(37.1, 121.1, 35)) + val ni2 = NodeInfo(5, MeshUser("+two", "User Two", "U2", model), Position(37.11, 121.1, 40)) + val ni3 = NodeInfo(6, MeshUser("+three", "User Three", "U3", model), Position(37.101, 121.1, 40)) private val currentDefaultLocale = LocaleListCompat.getDefault().get(0) diff --git a/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt b/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt new file mode 100644 index 000000000..4ac76e13f --- /dev/null +++ b/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt @@ -0,0 +1,16 @@ +package com.geeksville.mesh.model + +import android.net.Uri +import org.junit.Assert +import org.junit.Test + +class ChannelSetTest { + /** make sure we match the python and device code behavior */ + @Test + fun matchPython() { + val url = Uri.parse("https://www.meshtastic.org/d/#CgUYAyIBAQ") + val cs = ChannelSet(url) + Assert.assertEquals("LongSlow", cs.primaryChannel!!.name, ) + Assert.assertEquals("#LongSlow-V", cs.primaryChannel!!.humanName, ) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/geeksville/mesh/service/MeshServiceTest.kt b/app/src/test/java/com/geeksville/mesh/service/MeshServiceTest.kt index f8db6328c..aabea7598 100644 --- a/app/src/test/java/com/geeksville/mesh/service/MeshServiceTest.kt +++ b/app/src/test/java/com/geeksville/mesh/service/MeshServiceTest.kt @@ -1,31 +1,32 @@ -package com.geeksville.mesh.service - -import com.geeksville.mesh.MeshUser -import com.geeksville.mesh.NodeInfo -import com.geeksville.mesh.Position -import org.junit.Assert -import org.junit.Test - - -class MeshServiceTest { - - val nodeInfo = NodeInfo(4, MeshUser("+one", "User One", "U1"), Position(37.1, 121.1, 35, 10)) - - @Test - fun givenNodeInfo_whenUpdatingWithNewTime_thenPositionTimeIsUpdated() { - - val newerTime = 20 - updateNodeInfoTime(nodeInfo, newerTime) - Assert.assertEquals(newerTime, nodeInfo.position?.time) - } - - @Test - fun givenNodeInfo_whenUpdatingWithOldTime_thenPositionTimeIsNotUpdated() { - val olderTime = 5 - val timeBeforeTryingToUpdate = nodeInfo.position?.time - updateNodeInfoTime(nodeInfo, olderTime) - Assert.assertEquals(timeBeforeTryingToUpdate, nodeInfo.position?.time) - } -} - - +package com.geeksville.mesh.service + +import com.geeksville.mesh.MeshProtos +import com.geeksville.mesh.MeshUser +import com.geeksville.mesh.NodeInfo +import com.geeksville.mesh.Position +import org.junit.Assert +import org.junit.Test + + +class MeshServiceTest { + val model = MeshProtos.HardwareModel.ANDROID_SIM + val nodeInfo = NodeInfo(4, MeshUser("+one", "User One", "U1", model), Position(37.1, 121.1, 35, 10)) + + @Test + fun givenNodeInfo_whenUpdatingWithNewTime_thenPositionTimeIsUpdated() { + + val newerTime = 20 + updateNodeInfoTime(nodeInfo, newerTime) + Assert.assertEquals(newerTime, nodeInfo.position?.time) + } + + @Test + fun givenNodeInfo_whenUpdatingWithOldTime_thenPositionTimeIsNotUpdated() { + val olderTime = 5 + val timeBeforeTryingToUpdate = nodeInfo.position?.time + updateNodeInfoTime(nodeInfo, olderTime) + Assert.assertEquals(timeBeforeTryingToUpdate, nodeInfo.position?.time) + } +} + + diff --git a/build.gradle b/build.gradle index 183d7bf8d..ba99badad 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.2' + classpath 'com.android.tools.build:gradle:4.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" @@ -28,6 +28,9 @@ buildscript { //classpath "app.brant:amazonappstorepublisher:0.1.0" classpath 'com.github.triplet.gradle:play-publisher:2.8.0' + + // for unit testing https://github.com/bjoernQ/unmock-plugin + classpath 'com.github.bjoernq:unmockplugin:0.7.6' } } From 9889fa1da1be8e58b447e2c20f543ad19c93693c Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 19 Mar 2021 21:27:41 +0800 Subject: [PATCH 16/19] add channel url gen test --- app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt b/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt index 4ac76e13f..bf41360d8 100644 --- a/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt +++ b/app/src/test/java/com/geeksville/mesh/model/ChannelSetTest.kt @@ -12,5 +12,6 @@ class ChannelSetTest { val cs = ChannelSet(url) Assert.assertEquals("LongSlow", cs.primaryChannel!!.name, ) Assert.assertEquals("#LongSlow-V", cs.primaryChannel!!.humanName, ) + Assert.assertEquals(url, cs.getChannelUrl(false)) } } \ No newline at end of file From 54bf0e85c118feee12c0fb8bf2888697de7e05f4 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 19 Mar 2021 22:49:51 +0800 Subject: [PATCH 17/19] make 1.2 firmware update work even for old 1.0 devices --- .../java/com/geeksville/mesh/MainActivity.kt | 3 +-- .../geeksville/mesh/service/MeshService.kt | 23 ++++++++----------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index ecb07e1a1..1acff242c 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -668,8 +668,7 @@ class MainActivity : AppCompatActivity(), Logging, else { val curVer = DeviceVersion(info.firmwareVersion ?: "0.0.0") - val minVer = DeviceVersion("1.2.0") - if (curVer < minVer) + if (curVer < MeshService.minFirmwareVersion) showAlert(R.string.firmware_too_old, R.string.firmware_old) else { // If our app is too old/new, we probably don't understand the new radioconfig messages, so we don't read them until here 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 2567c93ba..15536e970 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -94,6 +94,10 @@ class MeshService : Service(), Logging { "com.geeksville.mesh", "com.geeksville.mesh.service.MeshService" ) + + /** The minimmum firmware version we know how to talk to. We'll still be able to talk to 1.0 firmwares but only well enough to ask them to firmware update + */ + val minFirmwareVersion = DeviceVersion("1.2.0") } enum class ConnectionState { @@ -1174,18 +1178,6 @@ class MeshService : Service(), Logging { /// Used to make sure we never get foold by old BLE packets private var configNonce = 1 - - private fun handleRadioConfig(radio: RadioConfigProtos.RadioConfig) { - val packetToSave = Packet( - UUID.randomUUID().toString(), - "RadioConfig", - System.currentTimeMillis(), - radio.toString() - ) - insertPacket(packetToSave) - radioConfig = radio - } - /** * Convert a protobuf NodeInfo into our model objects and update our node DB */ @@ -1416,7 +1408,12 @@ class MeshService : Service(), Logging { sendAnalytics() - requestRadioConfig() + if(deviceVersion < minFirmwareVersion) { + info("Device firmware is too old, faking config so firmware update can occur") + onHasSettings() + } + else + requestRadioConfig() } } else warn("Ignoring stale config complete") From 93f110158fd0861eb5f659a4653db78995c1bda9 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 19 Mar 2021 23:14:46 +0800 Subject: [PATCH 18/19] preserve even old 1.0 style region names during firmware update --- .../geeksville/mesh/service/MeshService.kt | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) 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 15536e970..b58f90d56 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -171,7 +171,7 @@ class MeshService : Service(), Logging { // FIXME - currently we don't support location reading without google play if (fusedLocationClient == null && isGooglePlayAvailable(this)) { GeeksvilleApplication.analytics.track("location_start") // Figure out how many users needed to use the phone GPS - + val request = LocationRequest.create().apply { interval = requestInterval priority = LocationRequest.PRIORITY_HIGH_ACCURACY @@ -949,13 +949,13 @@ class MeshService : Service(), Logging { } private var locationRequestInterval: Long = 0; - private fun setupLocationRequest () { + private fun setupLocationRequest() { val desiredInterval: Long = if (myNodeInfo?.hasGPS == true) { 0L // no requests when device has GPS - } else if (numOnlineNodes < 2) { + } else if (numOnlineNodes < 2) { 5 * 60 * 1000L // send infrequently, device needs these requests to set its clock } else { - radioConfig?.preferences?.positionBroadcastSecs?.times( 1000L) ?: 5 * 60 * 1000L + radioConfig?.preferences?.positionBroadcastSecs?.times(1000L) ?: 5 * 60 * 1000L } debug("desired location request $desiredInterval, current $locationRequestInterval") @@ -1288,6 +1288,9 @@ class MeshService : Service(), Logging { } } + /// If found, the old region string of the form 1.0-EU865 etc... + private var legacyRegion: String? = null + /** * Update the nodeinfo (called from either new API version or the old one) */ @@ -1301,6 +1304,7 @@ class MeshService : Service(), Logging { insertPacket(packetToSave) rawMyNodeInfo = myInfo + legacyRegion = myInfo.region regenMyNodeInfo() // We'll need to get a new set of channels and settings now @@ -1334,34 +1338,37 @@ class MeshService : Service(), Logging { } if (curRegionValue == RadioConfigProtos.RegionCode.Unset_VALUE) { - TODO("Need gui for setting region") - /* // look for a legacy region + // look for a legacy region val legacyRegex = Regex(".+-(.+)") - myNodeInfo?.region?.let { legacyRegion -> - val matches = legacyRegex.find(legacyRegion) + legacyRegion?.let { lr -> + val matches = legacyRegex.find(lr) if (matches != null) { val (region) = matches.destructured val newRegion = RadioConfigProtos.RegionCode.valueOf(region) info("Upgrading legacy region $newRegion (code ${newRegion.number})") curRegionValue = newRegion.number } - } */ + } } // If nothing was set in our (new style radio preferences, but we now have a valid setting - slam it in) if (curConfigRegion == RadioConfigProtos.RegionCode.Unset && curRegionValue != RadioConfigProtos.RegionCode.Unset_VALUE) { - info("Telling device to upgrade region") + if (deviceVersion >= minFirmwareVersion) { + info("Telling device to upgrade region") - // Tell the device to set the new region field (old devices will simply ignore this) - radioConfig?.let { currentConfig -> - val newConfig = currentConfig.toBuilder() + // Tell the device to set the new region field (old devices will simply ignore this) + radioConfig?.let { currentConfig -> + val newConfig = currentConfig.toBuilder() - val newPrefs = currentConfig.preferences.toBuilder() - newPrefs.regionValue = curRegionValue - newConfig.preferences = newPrefs.build() + val newPrefs = currentConfig.preferences.toBuilder() + newPrefs.regionValue = curRegionValue + newConfig.preferences = newPrefs.build() - sendRadioConfig(newConfig.build()) + sendRadioConfig(newConfig.build()) + } } + else + warn("Device is too old to understand region changes") } } } @@ -1405,14 +1412,13 @@ class MeshService : Service(), Logging { regenMyNodeInfo() // we have a node db now, so can possibly find a better hwmodel myNodeInfo = newMyNodeInfo // we might have just updated myNodeInfo - + sendAnalytics() - if(deviceVersion < minFirmwareVersion) { + if (deviceVersion < minFirmwareVersion) { info("Device firmware is too old, faking config so firmware update can occur") onHasSettings() - } - else + } else requestRadioConfig() } } else From 0ebb512dca9440d9d9b65a5a06c4d59a7744e9ab Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 20 Mar 2021 00:02:34 +0800 Subject: [PATCH 19/19] 1.2.11 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 527d63567..5cac80212 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,8 +37,8 @@ android { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) targetSdkVersion 29 - versionCode 20207 // format is Mmmss (where M is 1+the numeric major number - versionName "1.2.7" + versionCode 20211 // format is Mmmss (where M is 1+the numeric major number + versionName "1.2.11" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // per https://developer.android.com/studio/write/vector-asset-studio