Merge pull request #262 from geeksville/dev1.2

get ready for next release
pull/264/head 1.2.11
Kevin Hester 2021-03-20 00:03:01 +08:00 zatwierdzone przez GitHub
commit 57aff30082
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
10 zmienionych plików z 114 dodań i 104 usunięć

Wyświetl plik

@ -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 {
@ -31,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

Wyświetl plik

@ -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

Wyświetl plik

@ -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() }
@ -19,19 +11,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()
)
}
@ -78,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) })

Wyświetl plik

@ -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

Wyświetl plik

@ -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 {
@ -167,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
@ -945,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")
@ -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
*/
@ -1296,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)
*/
@ -1309,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
@ -1342,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")
}
}
}
@ -1413,10 +1412,14 @@ 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()
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")

Wyświetl plik

@ -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 =

Wyświetl plik

@ -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)

Wyświetl plik

@ -0,0 +1,17 @@
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, )
Assert.assertEquals(url, cs.getChannelUrl(false))
}
}

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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'
}
}