Merge branch 'osmdroid-phase3' of github.com:ScriptTactics/Meshtastic-Android into osmdroid-phase3

pull/505/head
PWRxPSYCHO 2022-10-19 09:38:11 -04:00
commit b07163adb7
16 zmienionych plików z 127 dodań i 146 usunięć

Wyświetl plik

@ -43,8 +43,8 @@ android {
applicationId "com.geeksville.mesh" applicationId "com.geeksville.mesh"
minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works)
targetSdkVersion 30 targetSdkVersion 30
versionCode 20342 // format is Mmmss (where M is 1+the numeric major number versionCode 20345 // format is Mmmss (where M is 1+the numeric major number
versionName "1.3.42" versionName "1.3.45"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// per https://developer.android.com/studio/write/vector-asset-studio // per https://developer.android.com/studio/write/vector-asset-studio

Wyświetl plik

@ -137,7 +137,7 @@
<!-- The QR codes to share channel settings are shared as meshtastic URLS <!-- The QR codes to share channel settings are shared as meshtastic URLS
an approximate example: an approximate example:
http://www.meshtastic.org/e/YXNkZnF3ZXJhc2RmcXdlcmFzZGZxd2Vy http://meshtastic.org/e/YXNkZnF3ZXJhc2RmcXdlcmFzZGZxd2Vy
--> -->
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -146,11 +146,11 @@
<data <data
android:scheme="https" android:scheme="https"
android:host="www.meshtastic.org" android:host="meshtastic.org"
android:pathPrefix="/e/" /> android:pathPrefix="/e/" />
<data <data
android:scheme="https" android:scheme="https"
android:host="www.meshtastic.org" android:host="meshtastic.org"
android:pathPrefix="/E/" /> android:pathPrefix="/E/" />
</intent-filter> </intent-filter>

Wyświetl plik

@ -72,20 +72,12 @@ interface IMeshService {
List<NodeInfo> getNodes(); List<NodeInfo> getNodes();
/// This method is only intended for use in our GUI, so the user can set radio options /// This method is only intended for use in our GUI, so the user can set radio options
/// It returns a DeviceConfig protobuf. /// It sets a Config protobuf via admin packet
byte []getDeviceConfig(); void setConfig(in byte []payload);
/// This method is only intended for use in our GUI, so the user can set radio options /// This method is only intended for use in our GUI, so the user can set radio options
/// It sets a DeviceConfig protobuf /// It sets a Channel protobuf via admin packet
void setDeviceConfig(in byte []payload); void setChannel(in byte []payload);
/// This method is only intended for use in our GUI, so the user can set radio options
/// It returns a ChannelSet protobuf.
byte []getChannels();
/// This method is only intended for use in our GUI, so the user can set radio options
/// It sets a ChannelSet protobuf
void setChannels(in byte []payload);
/// Send Shutdown admin packet to nodeNum /// Send Shutdown admin packet to nodeNum
void requestShutdown(in int idNum); void requestShutdown(in int idNum);

Wyświetl plik

@ -13,7 +13,6 @@ import android.text.method.LinkMovementMethod
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@ -40,6 +39,7 @@ import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.ChannelSet import com.geeksville.mesh.model.ChannelSet
import com.geeksville.mesh.model.DeviceVersion import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.repository.radio.BluetoothInterface
import com.geeksville.mesh.repository.radio.RadioInterfaceService import com.geeksville.mesh.repository.radio.RadioInterfaceService
import com.geeksville.mesh.repository.radio.SerialInterface import com.geeksville.mesh.repository.radio.SerialInterface
import com.geeksville.mesh.service.* import com.geeksville.mesh.service.*
@ -57,7 +57,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import java.nio.charset.Charset import java.nio.charset.Charset
import java.text.DateFormat import java.text.DateFormat
import java.util.* import java.util.Date
import javax.inject.Inject import javax.inject.Inject
/* /*
@ -302,8 +302,7 @@ class MainActivity : BaseActivity(), Logging {
} }
private fun initToolbar() { private fun initToolbar() {
val toolbar = val toolbar = binding.toolbar as Toolbar
findViewById<View>(R.id.toolbar) as Toolbar
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false) supportActionBar?.setDisplayShowTitleEnabled(false)
} }
@ -416,6 +415,7 @@ class MainActivity : BaseActivity(), Logging {
/** Show an alert that may contain HTML */ /** Show an alert that may contain HTML */
private fun showAlert(titleText: Int, messageText: Int) { private fun showAlert(titleText: Int, messageText: Int) {
// make links clickable per https://stackoverflow.com/a/62642807 // make links clickable per https://stackoverflow.com/a/62642807
// val messageStr = getText(messageText) // val messageStr = getText(messageText)
@ -476,6 +476,8 @@ class MainActivity : BaseActivity(), Logging {
} }
} }
} }
} else if (BluetoothInterface.invalidVersion) {
showAlert(R.string.firmware_too_old, R.string.firmware_old)
} }
} catch (ex: RemoteException) { } catch (ex: RemoteException) {
warn("Abandoning connect $ex, because we probably just lost device connection") warn("Abandoning connect $ex, because we probably just lost device connection")
@ -493,11 +495,7 @@ class MainActivity : BaseActivity(), Logging {
private fun showSnackbar(msgId: Int) { private fun showSnackbar(msgId: Int) {
try { try {
Snackbar.make( Snackbar.make(binding.root, msgId, Snackbar.LENGTH_LONG).show()
findViewById(android.R.id.content),
msgId,
Snackbar.LENGTH_LONG
).show()
} catch (ex: IllegalStateException) { } catch (ex: IllegalStateException) {
errormsg("Snackbar couldn't find view for msgId $msgId") errormsg("Snackbar couldn't find view for msgId $msgId")
} }
@ -505,11 +503,7 @@ class MainActivity : BaseActivity(), Logging {
private fun showSnackbar(msg: String) { private fun showSnackbar(msg: String) {
try { try {
Snackbar.make( Snackbar.make(binding.root, msg, Snackbar.LENGTH_INDEFINITE)
findViewById(android.R.id.content),
msg,
Snackbar.LENGTH_INDEFINITE
)
.apply { view.findViewById<TextView>(R.id.snackbar_text).isSingleLine = false } .apply { view.findViewById<TextView>(R.id.snackbar_text).isSingleLine = false }
.setAction(R.string.okay) { .setAction(R.string.okay) {
// dismiss // dismiss

Wyświetl plik

@ -29,7 +29,8 @@ data class Channel(
// The default channel that devices ship with // The default channel that devices ship with
val default = Channel( val default = Channel(
channelSettings { psk = ByteString.copyFrom(defaultPSK) }, channelSettings { psk = ByteString.copyFrom(defaultPSK) },
loRaConfig { usePreset = true; modemPreset = ModemPreset.LONG_FAST } // reference: NodeDB::installDefaultConfig
loRaConfig { txEnabled = true; modemPreset = ModemPreset.LONG_FAST; hopLimit = 3 }
) )
} }

Wyświetl plik

@ -15,7 +15,7 @@ data class ChannelSet(
) : Logging { ) : Logging {
companion object { companion object {
const val prefix = "https://www.meshtastic.org/e/#" const val prefix = "https://meshtastic.org/e/#"
private const val base64Flags = Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING private const val base64Flags = Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING
@ -65,7 +65,7 @@ data class ChannelSet(
// We encode as UPPER case for the QR code URL because QR codes are more efficient for that special case // We encode as UPPER case for the QR code URL because QR codes are more efficient for that special case
val bitMatrix = val bitMatrix =
multiFormatWriter.encode( multiFormatWriter.encode(
getChannelUrl(true).toString(), getChannelUrl(false).toString(),
BarcodeFormat.QR_CODE, BarcodeFormat.QR_CODE,
960, 960,
960 960

Wyświetl plik

@ -44,8 +44,9 @@ import java.io.BufferedWriter
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.FileWriter import java.io.FileWriter
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.max
import kotlin.math.roundToInt import kotlin.math.roundToInt
/// Given a human name, strip out the first letter of the first three words and return that as the initials for /// Given a human name, strip out the first letter of the first three words and return that as the initials for
@ -94,7 +95,6 @@ class UIViewModel @Inject constructor(
private val _channels = MutableStateFlow(ChannelSet()) private val _channels = MutableStateFlow(ChannelSet())
val channels: StateFlow<ChannelSet> = _channels val channels: StateFlow<ChannelSet> = _channels
val channelSet get() = channels.value.protobuf
private val _quickChatActions = MutableStateFlow<List<QuickChatAction>>(emptyList()) private val _quickChatActions = MutableStateFlow<List<QuickChatAction>>(emptyList())
val quickChatActions: StateFlow<List<QuickChatAction>> = _quickChatActions val quickChatActions: StateFlow<List<QuickChatAction>> = _quickChatActions
@ -271,48 +271,76 @@ class UIViewModel @Inject constructor(
inline fun updateDeviceConfig(crossinline body: (Config.DeviceConfig) -> Config.DeviceConfig) { inline fun updateDeviceConfig(crossinline body: (Config.DeviceConfig) -> Config.DeviceConfig) {
val data = body(config.device) val data = body(config.device)
setDeviceConfig(config { device = data }) setConfig(config { device = data })
} }
inline fun updatePositionConfig(crossinline body: (Config.PositionConfig) -> Config.PositionConfig) { inline fun updatePositionConfig(crossinline body: (Config.PositionConfig) -> Config.PositionConfig) {
val data = body(config.position) val data = body(config.position)
setDeviceConfig(config { position = data }) setConfig(config { position = data })
} }
inline fun updatePowerConfig(crossinline body: (Config.PowerConfig) -> Config.PowerConfig) { inline fun updatePowerConfig(crossinline body: (Config.PowerConfig) -> Config.PowerConfig) {
val data = body(config.power) val data = body(config.power)
setDeviceConfig(config { power = data }) setConfig(config { power = data })
} }
inline fun updateNetworkConfig(crossinline body: (Config.NetworkConfig) -> Config.NetworkConfig) { inline fun updateNetworkConfig(crossinline body: (Config.NetworkConfig) -> Config.NetworkConfig) {
val data = body(config.network) val data = body(config.network)
setDeviceConfig(config { network = data }) setConfig(config { network = data })
} }
inline fun updateDisplayConfig(crossinline body: (Config.DisplayConfig) -> Config.DisplayConfig) { inline fun updateDisplayConfig(crossinline body: (Config.DisplayConfig) -> Config.DisplayConfig) {
val data = body(config.display) val data = body(config.display)
setDeviceConfig(config { display = data }) setConfig(config { display = data })
} }
inline fun updateLoraConfig(crossinline body: (Config.LoRaConfig) -> Config.LoRaConfig) { inline fun updateLoraConfig(crossinline body: (Config.LoRaConfig) -> Config.LoRaConfig) {
val data = body(config.lora) val data = body(config.lora)
setDeviceConfig(config { lora = data }) setConfig(config { lora = data })
} }
inline fun updateBluetoothConfig(crossinline body: (Config.BluetoothConfig) -> Config.BluetoothConfig) { inline fun updateBluetoothConfig(crossinline body: (Config.BluetoothConfig) -> Config.BluetoothConfig) {
val data = body(config.bluetooth) val data = body(config.bluetooth)
setDeviceConfig(config { bluetooth = data }) setConfig(config { bluetooth = data })
} }
// Set the radio config (also updates our saved copy in preferences) // Set the radio config (also updates our saved copy in preferences)
fun setDeviceConfig(config: Config) { fun setConfig(config: Config) {
meshService?.deviceConfig = config.toByteArray() meshService?.setConfig(config.toByteArray())
} }
/// Convert the channels array to and from [AppOnlyProtos.ChannelSet]
private var _channelSet: AppOnlyProtos.ChannelSet
get() = channels.value.protobuf
set(value) {
(0 until max(_channelSet.settingsCount, value.settingsCount)).map { i ->
channel {
role = when (i) {
0 -> ChannelProtos.Channel.Role.PRIMARY
in 1 until value.settingsCount -> ChannelProtos.Channel.Role.SECONDARY
else -> ChannelProtos.Channel.Role.DISABLED
}
index = i
settings = value.settingsList.getOrNull(i) ?: channelSettings { }
}
}.forEach {
meshService?.setChannel(it.toByteArray())
}
viewModelScope.launch {
channelSetRepository.clearSettings()
channelSetRepository.addAllSettings(value)
}
val newConfig = config { lora = value.loraConfig }
if (config.lora != newConfig.lora) setConfig(newConfig)
}
val channelSet get() = _channelSet
/// Set the radio config (also updates our saved copy in preferences) /// Set the radio config (also updates our saved copy in preferences)
fun setChannels(c: ChannelSet) { fun setChannels(channelSet: ChannelSet) {
debug("Setting new channels!") debug("Setting new channels!")
meshService?.channels = c.protobuf.toByteArray() this._channelSet = channelSet.protobuf
} }
/// our name in hte radio /// our name in hte radio
@ -356,7 +384,8 @@ class UIViewModel @Inject constructor(
} }
} }
val adminChannelIndex: Int get() = channelSet.settingsList.map { it.name }.indexOf("admin") val adminChannelIndex: Int
get() = channelSet.settingsList.map { it.name.lowercase() }.indexOf("admin")
fun requestShutdown(idNum: Int) { fun requestShutdown(idNum: Int) {
try { try {

Wyświetl plik

@ -42,7 +42,7 @@ class ChannelSetRepository @Inject constructor(
suspend fun addSettings(channel: ChannelProtos.Channel) { suspend fun addSettings(channel: ChannelProtos.Channel) {
channelSetStore.updateData { preference -> channelSetStore.updateData { preference ->
preference.toBuilder().addSettings(channel.index, channel.settings).build() preference.toBuilder().addSettings(channel.settings).build()
} }
} }

Wyświetl plik

@ -99,8 +99,12 @@ class BluetoothInterface(
/// this service UUID is publically visible for scanning /// this service UUID is publically visible for scanning
val BTM_SERVICE_UUID: UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd") val BTM_SERVICE_UUID: UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd")
val BTM_FROMRADIO_CHARACTER: UUID = var invalidVersion = false
val EOL_FROMRADIO_CHARACTER: UUID =
UUID.fromString("8ba2bcc2-ee02-4a55-a531-c525c5e454d5") UUID.fromString("8ba2bcc2-ee02-4a55-a531-c525c5e454d5")
val BTM_FROMRADIO_CHARACTER: UUID =
UUID.fromString("2c55e69e-4993-11ed-b878-0242ac120002")
val BTM_TORADIO_CHARACTER: UUID = val BTM_TORADIO_CHARACTER: UUID =
UUID.fromString("f75c76d2-129e-4dad-a1dd-7866124401e7") UUID.fromString("f75c76d2-129e-4dad-a1dd-7866124401e7")
val BTM_FROMNUM_CHARACTER: UUID = val BTM_FROMNUM_CHARACTER: UUID =
@ -149,6 +153,7 @@ class BluetoothInterface(
?: throw RadioNotConnectedException("BLE service not found") ?: throw RadioNotConnectedException("BLE service not found")
private lateinit var fromNum: BluetoothGattCharacteristic private lateinit var fromNum: BluetoothGattCharacteristic
private lateinit var fromRadio: BluetoothGattCharacteristic
/** /**
* With the new rev2 api, our first send is to start the configure readbacks. In that case, * With the new rev2 api, our first send is to start the configure readbacks. In that case,
@ -228,7 +233,6 @@ class BluetoothInterface(
/// Attempt to read from the fromRadio mailbox, if data is found broadcast it to android apps /// Attempt to read from the fromRadio mailbox, if data is found broadcast it to android apps
private fun doReadFromRadio(firstRead: Boolean) { private fun doReadFromRadio(firstRead: Boolean) {
safe?.let { s -> safe?.let { s ->
val fromRadio = getCharacteristic(BTM_FROMRADIO_CHARACTER)
s.asyncReadCharacteristic(fromRadio) { s.asyncReadCharacteristic(fromRadio) {
try { try {
val b = it.getOrThrow() val b = it.getOrThrow()
@ -357,6 +361,16 @@ class BluetoothInterface(
fromNum = getCharacteristic(BTM_FROMNUM_CHARACTER) fromNum = getCharacteristic(BTM_FROMNUM_CHARACTER)
// We changed UUIDs to be able to identify old firmware (<1.3.43)
fromRadio = if (bservice.characteristics.map { it.uuid }
.contains(EOL_FROMRADIO_CHARACTER)) {
invalidVersion = true
getCharacteristic(EOL_FROMRADIO_CHARACTER)
} else {
invalidVersion = false
getCharacteristic(BTM_FROMRADIO_CHARACTER)
}
// We treat the first send by a client as special // We treat the first send by a client as special
isFirstSend = true isFirstSend = true

Wyświetl plik

@ -94,9 +94,6 @@ class MeshService : Service(), Logging {
class NodeNumNotFoundException(id: Int) : NodeNotFoundException("NodeNum not found $id") class NodeNumNotFoundException(id: Int) : NodeNotFoundException("NodeNum not found $id")
class IdNotFoundException(id: String) : NodeNotFoundException("ID not found $id") class IdNotFoundException(id: String) : NodeNotFoundException("ID not found $id")
class NoDeviceConfigException(message: String = "No radio settings received (is our app too old?)") :
RadioNotConnectedException(message)
/** We treat software update as similar to loss of comms to the regular bluetooth service (so things like sendPosition for background GPS ignores the problem */ /** We treat software update as similar to loss of comms to the regular bluetooth service (so things like sendPosition for background GPS ignores the problem */
class IsUpdatingException : class IsUpdatingException :
RadioNotConnectedException("Operation prohibited during firmware update") RadioNotConnectedException("Operation prohibited during firmware update")
@ -117,7 +114,7 @@ class MeshService : Service(), Logging {
/** 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 /** 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 minDeviceVersion = DeviceVersion("1.3.41") val minDeviceVersion = DeviceVersion("1.3.43")
} }
enum class ConnectionState { enum class ConnectionState {
@ -466,36 +463,6 @@ class MeshService : Service(), Logging {
/// Admin channel index /// Admin channel index
private var adminChannelIndex: Int = 0 private var adminChannelIndex: Int = 0
/// Convert the channels array into a ChannelSet
private var channelSet: AppOnlyProtos.ChannelSet
get() {
// this is never called
return AppOnlyProtos.ChannelSet.getDefaultInstance()
}
set(value) {
val asChannels = value.settingsList.mapIndexed { i, c ->
ChannelProtos.Channel.newBuilder().apply {
role =
if (i == 0) ChannelProtos.Channel.Role.PRIMARY else ChannelProtos.Channel.Role.SECONDARY
index = i
settings = c
}.build()
}
debug("Sending channels to device")
asChannels.forEach {
setChannel(it)
}
serviceScope.handledLaunch {
channelSetRepository.clearSettings()
channelSetRepository.addAllSettings(value)
}
val newConfig = config { lora = value.loraConfig }
if (localConfig.lora != newConfig.lora) sendDeviceConfig(newConfig)
}
/// Generate a new mesh packet builder with our node as the sender, and the specified node num /// 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 { private fun newMeshPacketTo(idNum: Int) = MeshPacket.newBuilder().apply {
if (myNodeInfo == null) if (myNodeInfo == null)
@ -745,28 +712,8 @@ class MeshService : Service(), Logging {
val ch = a.getChannelResponse val ch = a.getChannelResponse
debug("Admin: Received channel ${ch.index}") debug("Admin: Received channel ${ch.index}")
val packetToSave = MeshLog(
UUID.randomUUID().toString(),
"Channel",
System.currentTimeMillis(),
ch.toString()
)
insertMeshLog(packetToSave)
if (ch.index + 1 < mi.maxChannels) { if (ch.index + 1 < mi.maxChannels) {
handleChannel(ch)
// Stop once we get to the first disabled entry
if (/* ch.hasSettings() || */ ch.role != ChannelProtos.Channel.Role.DISABLED) {
// Not done yet, add new entries and request next channel
addChannelSettings(ch)
requestChannel(ch.index + 1)
} else {
debug("We've received the last channel, allowing rest of app to start...")
onHasSettings()
}
} else {
debug("Received max channels, starting app")
onHasSettings()
} }
} }
} }
@ -963,7 +910,7 @@ class MeshService : Service(), Logging {
} }
private fun addChannelSettings(ch: ChannelProtos.Channel) { private fun addChannelSettings(ch: ChannelProtos.Channel) {
if (ch.index == 0 || ch.settings.name == "admin") adminChannelIndex = ch.index if (ch.index == 0 || ch.settings.name.lowercase() == "admin") adminChannelIndex = ch.index
serviceScope.handledLaunch { serviceScope.handledLaunch {
channelSetRepository.addSettings(ch) channelSetRepository.addSettings(ch)
} }
@ -1143,6 +1090,7 @@ class MeshService : Service(), Logging {
MeshProtos.FromRadio.CONFIG_COMPLETE_ID_FIELD_NUMBER -> handleConfigComplete(proto.configCompleteId) MeshProtos.FromRadio.CONFIG_COMPLETE_ID_FIELD_NUMBER -> handleConfigComplete(proto.configCompleteId)
MeshProtos.FromRadio.MY_INFO_FIELD_NUMBER -> handleMyInfo(proto.myInfo) MeshProtos.FromRadio.MY_INFO_FIELD_NUMBER -> handleMyInfo(proto.myInfo)
MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> handleNodeInfo(proto.nodeInfo) MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> handleNodeInfo(proto.nodeInfo)
MeshProtos.FromRadio.CHANNEL_FIELD_NUMBER -> handleChannel(proto.channel)
MeshProtos.FromRadio.CONFIG_FIELD_NUMBER -> handleDeviceConfig(proto.config) MeshProtos.FromRadio.CONFIG_FIELD_NUMBER -> handleDeviceConfig(proto.config)
MeshProtos.FromRadio.MODULECONFIG_FIELD_NUMBER -> handleModuleConfig(proto.moduleConfig) MeshProtos.FromRadio.MODULECONFIG_FIELD_NUMBER -> handleModuleConfig(proto.moduleConfig)
else -> errormsg("Unexpected FromRadio variant") else -> errormsg("Unexpected FromRadio variant")
@ -1185,6 +1133,18 @@ class MeshService : Service(), Logging {
// setModuleConfig(config) // setModuleConfig(config)
} }
private fun handleChannel(ch: ChannelProtos.Channel) {
debug("Received channel ${ch.index}")
val packetToSave = MeshLog(
UUID.randomUUID().toString(),
"Channel",
System.currentTimeMillis(),
ch.toString()
)
insertMeshLog(packetToSave)
if (ch.role != ChannelProtos.Channel.Role.DISABLED) addChannelSettings(ch)
}
/** /**
* Convert a protobuf NodeInfo into our model objects and update our node DB * Convert a protobuf NodeInfo into our model objects and update our node DB
*/ */
@ -1361,8 +1321,8 @@ class MeshService : Service(), Logging {
if (deviceVersion < minDeviceVersion || appVersion < minAppVersion) { if (deviceVersion < minDeviceVersion || appVersion < minAppVersion) {
info("Device firmware or app is too old, faking config so firmware update can occur") info("Device firmware or app is too old, faking config so firmware update can occur")
clearLocalConfig() clearLocalConfig()
onHasSettings() }
} else requestChannel(0) // Now start reading channels onHasSettings()
} }
} else } else
warn("Ignoring stale config complete") warn("Ignoring stale config complete")
@ -1387,7 +1347,7 @@ class MeshService : Service(), Logging {
} }
private fun setChannel(ch: ChannelProtos.Channel) { private fun setChannel(ch: ChannelProtos.Channel) {
if (ch.index == 0 || ch.settings.name == "admin") adminChannelIndex = ch.index if (ch.index == 0 || ch.settings.name.lowercase() == "admin") adminChannelIndex = ch.index
sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket(wantResponse = true) { sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket(wantResponse = true) {
setChannel = ch setChannel = ch
}) })
@ -1424,6 +1384,9 @@ class MeshService : Service(), Logging {
configNonce += 1 configNonce += 1
newNodes.clear() newNodes.clear()
newMyNodeInfo = null newMyNodeInfo = null
if (BluetoothInterface.invalidVersion) onHasSettings() // Device firmware is too old
debug("Starting config nonce=$configNonce") debug("Starting config nonce=$configNonce")
sendToRadio(ToRadio.newBuilder().apply { sendToRadio(ToRadio.newBuilder().apply {
@ -1475,15 +1438,13 @@ class MeshService : Service(), Logging {
/** Send our current radio config to the device /** Send our current radio config to the device
*/ */
private fun sendDeviceConfig(c: ConfigProtos.Config) { private fun setConfig(config: ConfigProtos.Config) {
if (deviceVersion < minDeviceVersion) return if (deviceVersion < minDeviceVersion) return
debug("Setting new radio config!") debug("Setting new radio config!")
sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket { sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket {
setConfig = c setConfig = config
}) })
setLocalConfig(config) // Update our cached copy
// Update our cached copy
setLocalConfig(c)
} }
/** /**
@ -1698,23 +1659,14 @@ class MeshService : Service(), Logging {
} }
} }
override fun getDeviceConfig(): ByteArray = toRemoteExceptions { override fun setConfig(payload: ByteArray) = toRemoteExceptions {
this@MeshService.localConfig.toByteArray()
?: throw NoDeviceConfigException()
}
override fun setDeviceConfig(payload: ByteArray) = toRemoteExceptions {
val parsed = ConfigProtos.Config.parseFrom(payload) val parsed = ConfigProtos.Config.parseFrom(payload)
sendDeviceConfig(parsed) setConfig(parsed)
} }
override fun getChannels(): ByteArray = toRemoteExceptions { override fun setChannel(payload: ByteArray?) = toRemoteExceptions {
channelSet.toByteArray() val parsed = ChannelProtos.Channel.parseFrom(payload)
} setChannel(parsed)
override fun setChannels(payload: ByteArray?) = toRemoteExceptions {
val parsed = AppOnlyProtos.ChannelSet.parseFrom(payload)
channelSet = parsed
} }
override fun getNodes(): MutableList<NodeInfo> = toRemoteExceptions { override fun getNodes(): MutableList<NodeInfo> = toRemoteExceptions {

Wyświetl plik

@ -313,11 +313,12 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
} }
// No matter what apply the speed selection from the user // No matter what apply the speed selection from the user
val newLoRaConfig = loRaConfig { val newLoRaConfig = model.config.lora.copy {
region = model.region
txEnabled = model.txEnabled
usePreset = true usePreset = true
modemPreset = newModemPreset modemPreset = newModemPreset
bandwidth = 0
spreadFactor = 0
codingRate = 0
} }
val humanName = Channel(newSettings, newLoRaConfig).humanName val humanName = Channel(newSettings, newLoRaConfig).humanName

Wyświetl plik

@ -26,7 +26,6 @@ import com.geeksville.mesh.util.SqlTileWriterExt
import com.geeksville.mesh.util.SqlTileWriterExt.SourceCount import com.geeksville.mesh.util.SqlTileWriterExt.SourceCount
import com.geeksville.mesh.util.formatAgo import com.geeksville.mesh.util.formatAgo
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.osmdroid.api.IMapController import org.osmdroid.api.IMapController
import org.osmdroid.config.Configuration import org.osmdroid.config.Configuration
@ -57,7 +56,6 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener {
// UI Elements // UI Elements
private lateinit var binding: MapViewBinding private lateinit var binding: MapViewBinding
private lateinit var map: MapView private lateinit var map: MapView
private lateinit var downloadBtn: FloatingActionButton
private lateinit var cacheEstimate: TextView private lateinit var cacheEstimate: TextView
private lateinit var executeJob: Button private lateinit var executeJob: Button
private var downloadPrompt: AlertDialog? = null private var downloadPrompt: AlertDialog? = null
@ -96,8 +94,6 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener {
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View { ): View {
binding = MapViewBinding.inflate(inflater) binding = MapViewBinding.inflate(inflater)
downloadBtn = binding.root.findViewById(R.id.downloadButton)
binding.cacheLayout.visibility = View.GONE
return binding.root return binding.root
} }
@ -138,7 +134,7 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener {
} }
zoomToNodes(mapController) zoomToNodes(mapController)
} }
downloadBtn.setOnClickListener(this) binding.downloadButton.setOnClickListener(this)
} }
override fun onClick(v: View) { override fun onClick(v: View) {
@ -419,9 +415,9 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener {
private fun renderDownloadButton() { private fun renderDownloadButton() {
if (!(map.tileProvider.tileSource as OnlineTileSourceBase).tileSourcePolicy.acceptsBulkDownload()) { if (!(map.tileProvider.tileSource as OnlineTileSourceBase).tileSourcePolicy.acceptsBulkDownload()) {
downloadBtn.hide() binding.downloadButton.hide()
} else { } else {
downloadBtn.show() binding.downloadButton.show()
} }
} }

Wyświetl plik

@ -299,14 +299,15 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
model.localConfig.asLiveData().observe(viewLifecycleOwner) { model.localConfig.asLiveData().observe(viewLifecycleOwner) {
if (!model.isConnected()) { if (!model.isConnected()) {
val configCount = it.allFields.size val configCount = it.allFields.size
binding.scanStatusText.text = "Device config ($configCount / 7)" if (configCount > 0)
binding.scanStatusText.text = "Device config ($configCount / 7)"
} else updateNodeInfo() } else updateNodeInfo()
} }
model.channels.asLiveData().observe(viewLifecycleOwner) { model.channels.asLiveData().observe(viewLifecycleOwner) {
if (!model.isConnected()) { if (!model.isConnected()) it.protobuf.let { ch ->
val channelCount = it.protobuf.settingsCount if (!ch.hasLoraConfig() && ch.settingsCount > 0)
if (channelCount > 0) binding.scanStatusText.text = "Channels ($channelCount / 8)" binding.scanStatusText.text = "Channels (${ch.settingsCount} / 8)"
} }
} }

@ -1 +1 @@
Subproject commit d3dfaa63a5108c1da7571cd780efaf561b99cc74 Subproject commit c85caacf3c92717ad5547927c784cbe527ee1d74

Wyświetl plik

@ -28,6 +28,7 @@
android:id="@+id/cache_layout" android:id="@+id/cache_layout"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"> app:layout_constraintStart_toStartOf="parent">

Wyświetl plik

@ -8,10 +8,10 @@ class ChannelSetTest {
/** make sure we match the python and device code behavior */ /** make sure we match the python and device code behavior */
@Test @Test
fun matchPython() { fun matchPython() {
val url = Uri.parse("https://www.meshtastic.org/e/#CgUYAiIBAQ") val url = Uri.parse("https://meshtastic.org/e/#CgMSAQESAA")
val cs = ChannelSet(url) val cs = ChannelSet(url)
Assert.assertEquals("LongFast", cs.primaryChannel!!.name) Assert.assertEquals("LongFast", cs.primaryChannel!!.name)
Assert.assertEquals("#LongFast-K", cs.primaryChannel!!.humanName) Assert.assertEquals("#LongFast-I", cs.primaryChannel!!.humanName)
Assert.assertEquals(url, cs.getChannelUrl(false)) Assert.assertEquals(url, cs.getChannelUrl(false))
} }
} }