mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-03-31 12:11:15 -04:00
add ModuleConfig settings (#526)
This commit is contained in:
@@ -75,6 +75,10 @@ interface IMeshService {
|
||||
/// It sets a Config protobuf via admin packet
|
||||
void setConfig(in byte []payload);
|
||||
|
||||
/// This method is only intended for use in our GUI, so the user can set radio options
|
||||
/// It sets a ModuleConfig protobuf via admin packet
|
||||
void setModuleConfig(in byte []payload);
|
||||
|
||||
/// This method is only intended for use in our GUI, so the user can set radio options
|
||||
/// It sets a Channel protobuf via admin packet
|
||||
void setChannel(in byte []payload);
|
||||
|
||||
@@ -761,13 +761,18 @@ class MainActivity : BaseActivity(), Logging {
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
return true
|
||||
}
|
||||
R.id.advanced_settings -> {
|
||||
val fragmentManager: FragmentManager = supportFragmentManager
|
||||
val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()
|
||||
val nameFragment = AdvancedSettingsFragment()
|
||||
fragmentTransaction.add(R.id.mainActivityLayout, nameFragment)
|
||||
fragmentTransaction.addToBackStack(null)
|
||||
fragmentTransaction.commit()
|
||||
R.id.device_settings -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(R.id.mainActivityLayout, DeviceSettingsFragment())
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
return true
|
||||
}
|
||||
R.id.module_settings -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(R.id.mainActivityLayout, ModuleSettingsFragment())
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
return true
|
||||
}
|
||||
R.id.save_messages_csv -> {
|
||||
|
||||
@@ -15,15 +15,18 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.*
|
||||
import com.geeksville.mesh.ConfigProtos.Config
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig
|
||||
import com.geeksville.mesh.database.MeshLogRepository
|
||||
import com.geeksville.mesh.database.QuickChatActionRepository
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.database.entity.MeshLog
|
||||
import com.geeksville.mesh.database.entity.QuickChatAction
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig
|
||||
import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.repository.datastore.ChannelSetRepository
|
||||
import com.geeksville.mesh.repository.datastore.LocalConfigRepository
|
||||
import com.geeksville.mesh.repository.datastore.ModuleConfigRepository
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.util.GPSFormat
|
||||
import com.geeksville.mesh.util.positionToMeter
|
||||
@@ -81,6 +84,7 @@ class UIViewModel @Inject constructor(
|
||||
private val channelSetRepository: ChannelSetRepository,
|
||||
private val packetRepository: PacketRepository,
|
||||
private val localConfigRepository: LocalConfigRepository,
|
||||
private val moduleConfigRepository: ModuleConfigRepository,
|
||||
private val quickChatActionRepository: QuickChatActionRepository,
|
||||
private val preferences: SharedPreferences
|
||||
) : ViewModel(), Logging {
|
||||
@@ -95,6 +99,10 @@ class UIViewModel @Inject constructor(
|
||||
val localConfig: StateFlow<LocalConfig> = _localConfig
|
||||
val config get() = _localConfig.value
|
||||
|
||||
private val _moduleConfig = MutableStateFlow<LocalModuleConfig>(LocalModuleConfig.getDefaultInstance())
|
||||
val moduleConfig: StateFlow<LocalModuleConfig> = _moduleConfig
|
||||
val module get() = _moduleConfig.value
|
||||
|
||||
private val _channels = MutableStateFlow(ChannelSet())
|
||||
val channels: StateFlow<ChannelSet> = _channels
|
||||
|
||||
@@ -115,6 +123,9 @@ class UIViewModel @Inject constructor(
|
||||
localConfigRepository.localConfigFlow.onEach { config ->
|
||||
_localConfig.value = config
|
||||
}.launchIn(viewModelScope)
|
||||
moduleConfigRepository.moduleConfigFlow.onEach { config ->
|
||||
_moduleConfig.value = config
|
||||
}.launchIn(viewModelScope)
|
||||
viewModelScope.launch {
|
||||
quickChatActionRepository.getAllActions().collect { actions ->
|
||||
_quickChatActions.value = actions
|
||||
@@ -238,6 +249,7 @@ class UIViewModel @Inject constructor(
|
||||
val isRouter: Boolean = config.device.role == Config.DeviceConfig.Role.ROUTER
|
||||
|
||||
// We consider hasWifi = ESP32
|
||||
fun hasGPS() = myNodeInfo.value?.hasGPS == true
|
||||
fun hasWifi() = myNodeInfo.value?.hasWifi == true
|
||||
|
||||
/// hardware info about our local device (can be null)
|
||||
@@ -311,6 +323,50 @@ class UIViewModel @Inject constructor(
|
||||
meshService?.setConfig(config.toByteArray())
|
||||
}
|
||||
|
||||
inline fun updateMQTTConfig(crossinline body: (ModuleConfig.MQTTConfig) -> ModuleConfig.MQTTConfig) {
|
||||
val data = body(module.mqtt)
|
||||
setModuleConfig(moduleConfig { mqtt = data })
|
||||
}
|
||||
|
||||
inline fun updateSerialConfig(crossinline body: (ModuleConfig.SerialConfig) -> ModuleConfig.SerialConfig) {
|
||||
val data = body(module.serial)
|
||||
setModuleConfig(moduleConfig { serial = data })
|
||||
}
|
||||
|
||||
inline fun updateExternalNotificationConfig(crossinline body: (ModuleConfig.ExternalNotificationConfig) -> ModuleConfig.ExternalNotificationConfig) {
|
||||
val data = body(module.externalNotification)
|
||||
setModuleConfig(moduleConfig { externalNotification = data })
|
||||
}
|
||||
|
||||
inline fun updateStoreForwardConfig(crossinline body: (ModuleConfig.StoreForwardConfig) -> ModuleConfig.StoreForwardConfig) {
|
||||
val data = body(module.storeForward)
|
||||
setModuleConfig(moduleConfig { storeForward = data })
|
||||
}
|
||||
|
||||
inline fun updateRangeTestConfig(crossinline body: (ModuleConfig.RangeTestConfig) -> ModuleConfig.RangeTestConfig) {
|
||||
val data = body(module.rangeTest)
|
||||
setModuleConfig(moduleConfig { rangeTest = data })
|
||||
}
|
||||
|
||||
inline fun updateTelemetryConfig(crossinline body: (ModuleConfig.TelemetryConfig) -> ModuleConfig.TelemetryConfig) {
|
||||
val data = body(module.telemetry)
|
||||
setModuleConfig(moduleConfig { telemetry = data })
|
||||
}
|
||||
|
||||
inline fun updateCannedMessageConfig(crossinline body: (ModuleConfig.CannedMessageConfig) -> ModuleConfig.CannedMessageConfig) {
|
||||
val data = body(module.cannedMessage)
|
||||
setModuleConfig(moduleConfig { cannedMessage = data })
|
||||
}
|
||||
|
||||
inline fun updateAudioConfig(crossinline body: (ModuleConfig.AudioConfig) -> ModuleConfig.AudioConfig) {
|
||||
val data = body(module.audio)
|
||||
setModuleConfig(moduleConfig { audio = data })
|
||||
}
|
||||
|
||||
fun setModuleConfig(config: ModuleConfig) {
|
||||
meshService?.setModuleConfig(config.toByteArray())
|
||||
}
|
||||
|
||||
/// Convert the channels array to and from [AppOnlyProtos.ChannelSet]
|
||||
private var _channelSet: AppOnlyProtos.ChannelSet
|
||||
get() = channels.value.protobuf
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.geeksville.mesh.AppOnlyProtos.ChannelSet
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -34,6 +35,19 @@ object DataStoreModule {
|
||||
)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideModuleConfigDataStore(@ApplicationContext appContext: Context): DataStore<LocalModuleConfig> {
|
||||
return DataStoreFactory.create(
|
||||
serializer = ModuleConfigSerializer,
|
||||
produceFile = { appContext.dataStoreFile("module_config.pb") },
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(
|
||||
produceNewData = { LocalModuleConfig.getDefaultInstance() }
|
||||
),
|
||||
scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideChannelSetDataStore(@ApplicationContext appContext: Context): DataStore<ChannelSet> {
|
||||
|
||||
@@ -2,13 +2,13 @@ package com.geeksville.mesh.repository.datastore
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.ConfigProtos.Config
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -30,20 +30,15 @@ class LocalConfigRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val _sendDeviceConfigFlow = MutableSharedFlow<ConfigProtos.Config>()
|
||||
val sendDeviceConfigFlow = _sendDeviceConfigFlow.asSharedFlow()
|
||||
|
||||
private suspend fun sendDeviceConfig(config: ConfigProtos.Config) {
|
||||
debug("Sending device config!")
|
||||
_sendDeviceConfigFlow.emit(config)
|
||||
}
|
||||
private val _setConfigFlow = MutableSharedFlow<Config>()
|
||||
val setConfigFlow: SharedFlow<Config> = _setConfigFlow
|
||||
|
||||
/**
|
||||
* Update LocalConfig and send ConfigProtos.Config Oneof to the radio
|
||||
*/
|
||||
suspend fun setRadioConfig(config: ConfigProtos.Config) {
|
||||
suspend fun setConfig(config: Config) {
|
||||
setLocalConfig(config)
|
||||
sendDeviceConfig(config)
|
||||
_setConfigFlow.emit(config)
|
||||
}
|
||||
|
||||
suspend fun clearLocalConfig() {
|
||||
@@ -55,7 +50,7 @@ class LocalConfigRepository @Inject constructor(
|
||||
/**
|
||||
* Update LocalConfig from each ConfigProtos.Config Oneof
|
||||
*/
|
||||
suspend fun setLocalConfig(config: ConfigProtos.Config) {
|
||||
suspend fun setLocalConfig(config: Config) {
|
||||
if (config.hasDevice()) setDeviceConfig(config.device)
|
||||
if (config.hasPosition()) setPositionConfig(config.position)
|
||||
if (config.hasPower()) setPowerConfig(config.power)
|
||||
@@ -65,44 +60,44 @@ class LocalConfigRepository @Inject constructor(
|
||||
if (config.hasBluetooth()) setBluetoothConfig(config.bluetooth)
|
||||
}
|
||||
|
||||
private suspend fun setDeviceConfig(config: ConfigProtos.Config.DeviceConfig) {
|
||||
private suspend fun setDeviceConfig(config: Config.DeviceConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setDevice(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setPositionConfig(config: ConfigProtos.Config.PositionConfig) {
|
||||
private suspend fun setPositionConfig(config: Config.PositionConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setPosition(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setPowerConfig(config: ConfigProtos.Config.PowerConfig) {
|
||||
private suspend fun setPowerConfig(config: Config.PowerConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setPower(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setWifiConfig(config: ConfigProtos.Config.NetworkConfig) {
|
||||
private suspend fun setWifiConfig(config: Config.NetworkConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setNetwork(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setDisplayConfig(config: ConfigProtos.Config.DisplayConfig) {
|
||||
private suspend fun setDisplayConfig(config: Config.DisplayConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setDisplay(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setLoraConfig(config: ConfigProtos.Config.LoRaConfig) {
|
||||
private suspend fun setLoraConfig(config: Config.LoRaConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setLora(config).build()
|
||||
}
|
||||
channelSetRepository.setLoraConfig(config)
|
||||
}
|
||||
|
||||
private suspend fun setBluetoothConfig(config: ConfigProtos.Config.BluetoothConfig) {
|
||||
private suspend fun setBluetoothConfig(config: Config.BluetoothConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setBluetooth(config).build()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.geeksville.mesh.repository.datastore
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.first
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Class that handles saving and retrieving config settings
|
||||
*/
|
||||
class ModuleConfigRepository @Inject constructor(
|
||||
private val moduleConfigStore: DataStore<LocalModuleConfig>,
|
||||
) : Logging {
|
||||
val moduleConfigFlow: Flow<LocalModuleConfig> = moduleConfigStore.data
|
||||
.catch { exception ->
|
||||
// dataStore.data throws an IOException when an error is encountered when reading data
|
||||
if (exception is IOException) {
|
||||
errormsg("Error reading LocalConfig settings: ${exception.message}")
|
||||
emit(LocalModuleConfig.getDefaultInstance())
|
||||
} else {
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clearLocalModuleConfig() {
|
||||
moduleConfigStore.updateData { preference ->
|
||||
preference.toBuilder().clear().build()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update LocalModuleConfig from each ModuleConfigProtos.ModuleConfig Oneof
|
||||
*/
|
||||
suspend fun setLocalModuleConfig(config: ModuleConfig) {
|
||||
if (config.hasMqtt()) setMQTTConfig(config.mqtt)
|
||||
if (config.hasSerial()) setSerialConfig(config.serial)
|
||||
if (config.hasExternalNotification()) setExternalNotificationConfig(config.externalNotification)
|
||||
if (config.hasStoreForward()) setStoreForwardConfig(config.storeForward)
|
||||
if (config.hasRangeTest()) setRangeTestConfig(config.rangeTest)
|
||||
if (config.hasTelemetry()) setTelemetryConfig(config.telemetry)
|
||||
if (config.hasCannedMessage()) setCannedMessageConfig(config.cannedMessage)
|
||||
if (config.hasAudio()) setAudioConfig(config.audio)
|
||||
}
|
||||
|
||||
private suspend fun setMQTTConfig(config: ModuleConfig.MQTTConfig) {
|
||||
moduleConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setMqtt(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setSerialConfig(config: ModuleConfig.SerialConfig) {
|
||||
moduleConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setSerial(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setExternalNotificationConfig(config: ModuleConfig.ExternalNotificationConfig) {
|
||||
moduleConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setExternalNotification(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setStoreForwardConfig(config: ModuleConfig.StoreForwardConfig) {
|
||||
moduleConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setStoreForward(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setRangeTestConfig(config: ModuleConfig.RangeTestConfig) {
|
||||
moduleConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setRangeTest(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setTelemetryConfig(config: ModuleConfig.TelemetryConfig) {
|
||||
moduleConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setTelemetry(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setCannedMessageConfig(config: ModuleConfig.CannedMessageConfig) {
|
||||
moduleConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setCannedMessage(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setAudioConfig(config: ModuleConfig.AudioConfig) {
|
||||
moduleConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setAudio(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchInitialModuleConfig() = moduleConfigStore.data.first()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.geeksville.mesh.repository.datastore
|
||||
|
||||
import androidx.datastore.core.CorruptionException
|
||||
import androidx.datastore.core.Serializer
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* Serializer for the [LocalModuleConfig] object defined in localonly.proto.
|
||||
*/
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
object ModuleConfigSerializer : Serializer<LocalModuleConfig> {
|
||||
override val defaultValue: LocalModuleConfig = LocalModuleConfig.getDefaultInstance()
|
||||
|
||||
override suspend fun readFrom(input: InputStream): LocalModuleConfig {
|
||||
try {
|
||||
return LocalModuleConfig.parseFrom(input)
|
||||
} catch (exception: InvalidProtocolBufferException) {
|
||||
throw CorruptionException("Cannot read proto.", exception)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun writeTo(t: LocalModuleConfig, output: OutputStream) = t.writeTo(output)
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.repository.datastore.ChannelSetRepository
|
||||
import com.geeksville.mesh.repository.datastore.LocalConfigRepository
|
||||
import com.geeksville.mesh.repository.datastore.ModuleConfigRepository
|
||||
import com.geeksville.mesh.repository.location.LocationRepository
|
||||
import com.geeksville.mesh.repository.radio.BluetoothInterface
|
||||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||
@@ -66,6 +67,9 @@ class MeshService : Service(), Logging {
|
||||
@Inject
|
||||
lateinit var localConfigRepository: LocalConfigRepository
|
||||
|
||||
@Inject
|
||||
lateinit var moduleConfigRepository: ModuleConfigRepository
|
||||
|
||||
@Inject
|
||||
lateinit var channelSetRepository: ChannelSetRepository
|
||||
|
||||
@@ -902,9 +906,16 @@ class MeshService : Service(), Logging {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLocalModuleConfig(config: ModuleConfigProtos.ModuleConfig) {
|
||||
serviceScope.handledLaunch {
|
||||
moduleConfigRepository.setLocalModuleConfig(config)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearLocalConfig() {
|
||||
serviceScope.handledLaunch {
|
||||
localConfigRepository.clearLocalConfig()
|
||||
moduleConfigRepository.clearLocalModuleConfig()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1120,16 +1131,16 @@ class MeshService : Service(), Logging {
|
||||
setLocalConfig(config)
|
||||
}
|
||||
|
||||
private fun handleModuleConfig(module: ModuleConfigProtos.ModuleConfig) {
|
||||
debug("Received moduleConfig ${module.toOneLineString()}")
|
||||
private fun handleModuleConfig(config: ModuleConfigProtos.ModuleConfig) {
|
||||
debug("Received moduleConfig ${config.toOneLineString()}")
|
||||
val packetToSave = MeshLog(
|
||||
UUID.randomUUID().toString(),
|
||||
"ModuleConfig ${module.payloadVariantCase}",
|
||||
"ModuleConfig ${config.payloadVariantCase}",
|
||||
System.currentTimeMillis(),
|
||||
module.toString()
|
||||
config.toString()
|
||||
)
|
||||
insertMeshLog(packetToSave)
|
||||
// setModuleConfig(config)
|
||||
setLocalModuleConfig(config)
|
||||
}
|
||||
|
||||
private fun handleChannel(ch: ChannelProtos.Channel) {
|
||||
@@ -1275,6 +1286,7 @@ class MeshService : Service(), Logging {
|
||||
serviceScope.handledLaunch {
|
||||
channelSetRepository.clearChannelSet()
|
||||
localConfigRepository.clearLocalConfig()
|
||||
moduleConfigRepository.clearLocalModuleConfig()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1446,7 +1458,18 @@ class MeshService : Service(), Logging {
|
||||
sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket {
|
||||
setConfig = config
|
||||
})
|
||||
setLocalConfig(config) // Update our cached copy
|
||||
setLocalConfig(config) // Update our local copy
|
||||
}
|
||||
|
||||
/** Send our current module config to the device
|
||||
*/
|
||||
private fun setModuleConfig(config: ModuleConfigProtos.ModuleConfig) {
|
||||
if (deviceVersion < minDeviceVersion) return
|
||||
debug("Setting new module config!")
|
||||
sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket {
|
||||
setModuleConfig = config
|
||||
})
|
||||
setLocalModuleConfig(config) // Update our local copy
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1667,6 +1690,11 @@ class MeshService : Service(), Logging {
|
||||
setConfig(parsed)
|
||||
}
|
||||
|
||||
override fun setModuleConfig(payload: ByteArray) = toRemoteExceptions {
|
||||
val parsed = ModuleConfigProtos.ModuleConfig.parseFrom(payload)
|
||||
setModuleConfig(parsed)
|
||||
}
|
||||
|
||||
override fun setChannel(payload: ByteArray?) = toRemoteExceptions {
|
||||
val parsed = ChannelProtos.Channel.parseFrom(payload)
|
||||
setChannel(parsed)
|
||||
|
||||
@@ -7,33 +7,31 @@ import android.view.ViewGroup
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.databinding.AdvancedSettingsBinding
|
||||
import com.geeksville.mesh.databinding.ComposeViewBinding
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
|
||||
|
||||
private var _binding: AdvancedSettingsBinding? = null
|
||||
class DeviceSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
|
||||
|
||||
private var _binding: ComposeViewBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val model: UIViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = AdvancedSettingsBinding.inflate(inflater, container, false)
|
||||
_binding = ComposeViewBinding.inflate(inflater, container, false)
|
||||
.apply {
|
||||
deviceConfig.apply {
|
||||
composeView.apply {
|
||||
setViewCompositionStrategy(
|
||||
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
|
||||
)
|
||||
setContent {
|
||||
MdcTheme {
|
||||
PreferenceScreen(model)
|
||||
DeviceSettingsItemList(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import com.geeksville.mesh.ui.components.RegularPreference
|
||||
import com.geeksville.mesh.ui.components.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun PreferenceItemList(viewModel: UIViewModel) {
|
||||
fun DeviceSettingsItemList(viewModel: UIViewModel) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
val hasWifi = viewModel.hasWifi()
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.databinding.ComposeViewBinding
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ModuleSettingsFragment : ScreenFragment("Module Settings"), Logging {
|
||||
|
||||
private var _binding: ComposeViewBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private val model: UIViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = ComposeViewBinding.inflate(inflater, container, false)
|
||||
.apply {
|
||||
composeView.apply {
|
||||
setViewCompositionStrategy(
|
||||
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
|
||||
)
|
||||
setContent {
|
||||
MdcTheme {
|
||||
ModuleSettingsItemList(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
package com.geeksville.mesh.ui
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.ui.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.components.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun ModuleSettingsItemList(viewModel: UIViewModel) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
val connectionState by viewModel.connectionState.observeAsState()
|
||||
val connected = connectionState == MeshService.ConnectionState.CONNECTED
|
||||
|
||||
val moduleConfig by viewModel.moduleConfig.collectAsState()
|
||||
|
||||
// Temporary [ModuleConfigProtos.ModuleConfig] state holders
|
||||
var mqttInput by remember(moduleConfig.mqtt) { mutableStateOf(moduleConfig.mqtt) }
|
||||
var serialInput by remember(moduleConfig.serial) { mutableStateOf(moduleConfig.serial) }
|
||||
var externalNotificationInput by remember(moduleConfig.externalNotification) { mutableStateOf(moduleConfig.externalNotification) }
|
||||
var storeForwardInput by remember(moduleConfig.storeForward) { mutableStateOf(moduleConfig.storeForward) }
|
||||
var rangeTestInput by remember(moduleConfig.rangeTest) { mutableStateOf(moduleConfig.rangeTest) }
|
||||
var telemetryInput by remember(moduleConfig.telemetry) { mutableStateOf(moduleConfig.telemetry) }
|
||||
var cannedMessageInput by remember(moduleConfig.cannedMessage) { mutableStateOf(moduleConfig.cannedMessage) }
|
||||
var audioInput by remember(moduleConfig.audio) { mutableStateOf(moduleConfig.audio) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
item { PreferenceCategory(text = "MQTT Config") }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "MQTT enabled",
|
||||
checked = mqttInput.enabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { enabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Address",
|
||||
value = mqttInput.address,
|
||||
enabled = connected,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { value ->
|
||||
if (value.toByteArray().size <= 31) // address max_size:32
|
||||
mqttInput = mqttInput.copy { address = value }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Username",
|
||||
value = mqttInput.username,
|
||||
enabled = connected,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { value ->
|
||||
if (value.toByteArray().size <= 31) // username max_size:32
|
||||
mqttInput = mqttInput.copy { username = value }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Password",
|
||||
value = mqttInput.password,
|
||||
enabled = connected,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { value ->
|
||||
if (value.toByteArray().size <= 31) // password max_size:32
|
||||
mqttInput = mqttInput.copy { password = value }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Encryption enabled",
|
||||
checked = mqttInput.encryptionEnabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { encryptionEnabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "JSON output enabled",
|
||||
checked = mqttInput.jsonEnabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { jsonEnabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = mqttInput != moduleConfig.mqtt,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
mqttInput = moduleConfig.mqtt
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateMQTTConfig { mqttInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "Serial Config") }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Serial enabled",
|
||||
checked = serialInput.enabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { serialInput = serialInput.copy { enabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Echo enabled",
|
||||
checked = serialInput.echo,
|
||||
enabled = connected,
|
||||
onCheckedChange = { serialInput = serialInput.copy { echo = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "RX",
|
||||
value = serialInput.rxd,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { serialInput = serialInput.copy { rxd = it } })
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "TX",
|
||||
value = serialInput.txd,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { serialInput = serialInput.copy { txd = it } })
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "Serial baud rate",
|
||||
enabled = connected,
|
||||
items = ModuleConfig.SerialConfig.Serial_Baud.values()
|
||||
.filter { it != ModuleConfig.SerialConfig.Serial_Baud.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = serialInput.baud,
|
||||
onItemSelected = { serialInput = serialInput.copy { baud = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Timeout",
|
||||
value = serialInput.timeout,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { serialInput = serialInput.copy { timeout = it } })
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "Serial baud rate",
|
||||
enabled = connected,
|
||||
items = ModuleConfig.SerialConfig.Serial_Mode.values()
|
||||
.filter { it != ModuleConfig.SerialConfig.Serial_Mode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = serialInput.mode,
|
||||
onItemSelected = { serialInput = serialInput.copy { mode = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = serialInput != moduleConfig.serial,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
serialInput = moduleConfig.serial
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateSerialConfig { serialInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "External Notification Config") }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "External Notification enabled",
|
||||
checked = externalNotificationInput.enabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { enabled = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Output milliseconds",
|
||||
value = externalNotificationInput.outputMs,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
externalNotificationInput = externalNotificationInput.copy { outputMs = it }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Output",
|
||||
value = externalNotificationInput.output,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
externalNotificationInput = externalNotificationInput.copy { output = it }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Active",
|
||||
checked = externalNotificationInput.active,
|
||||
enabled = connected,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { active = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Alert message",
|
||||
checked = externalNotificationInput.alertMessage,
|
||||
enabled = connected,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { alertMessage = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Alert bell",
|
||||
checked = externalNotificationInput.alertBell,
|
||||
enabled = connected,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { alertBell = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = externalNotificationInput != moduleConfig.externalNotification,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
externalNotificationInput = moduleConfig.externalNotification
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateExternalNotificationConfig { externalNotificationInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "Store & Forward Config") }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Store & Forward enabled",
|
||||
checked = storeForwardInput.enabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { enabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Heartbeat",
|
||||
checked = storeForwardInput.heartbeat,
|
||||
enabled = connected,
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { heartbeat = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Number of records",
|
||||
value = storeForwardInput.records,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { storeForwardInput = storeForwardInput.copy { records = it } })
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "History return max",
|
||||
value = storeForwardInput.historyReturnMax,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
storeForwardInput = storeForwardInput.copy { historyReturnMax = it }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "History return window",
|
||||
value = storeForwardInput.historyReturnWindow,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
storeForwardInput = storeForwardInput.copy { historyReturnWindow = it }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = storeForwardInput != moduleConfig.storeForward,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
storeForwardInput = moduleConfig.storeForward
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateStoreForwardConfig { storeForwardInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "Range Test Config") }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Range Test enabled",
|
||||
checked = rangeTestInput.enabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { rangeTestInput = rangeTestInput.copy { enabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Sender message interval",
|
||||
value = rangeTestInput.sender,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { rangeTestInput = rangeTestInput.copy { sender = it } })
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Save .CSV in storage (ESP32 only)",
|
||||
checked = rangeTestInput.save,
|
||||
enabled = connected,
|
||||
onCheckedChange = { rangeTestInput = rangeTestInput.copy { save = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = rangeTestInput != moduleConfig.rangeTest,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
rangeTestInput = moduleConfig.rangeTest
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateRangeTestConfig { rangeTestInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "Telemetry Config") }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Device metrics update interval",
|
||||
value = telemetryInput.deviceUpdateInterval,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
telemetryInput = telemetryInput.copy { deviceUpdateInterval = it }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Environment metrics update interval",
|
||||
value = telemetryInput.environmentUpdateInterval,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
telemetryInput = telemetryInput.copy { environmentUpdateInterval = it }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Environment metrics module enabled",
|
||||
checked = telemetryInput.environmentMeasurementEnabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = {
|
||||
telemetryInput = telemetryInput.copy { environmentMeasurementEnabled = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Environment metrics on-screen enabled",
|
||||
checked = telemetryInput.environmentScreenEnabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = {
|
||||
telemetryInput = telemetryInput.copy { environmentScreenEnabled = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Environment metrics use Fahrenheit",
|
||||
checked = telemetryInput.environmentDisplayFahrenheit,
|
||||
enabled = connected,
|
||||
onCheckedChange = {
|
||||
telemetryInput = telemetryInput.copy { environmentDisplayFahrenheit = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = telemetryInput != moduleConfig.telemetry,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
telemetryInput = moduleConfig.telemetry
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateTelemetryConfig { telemetryInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "Canned Message Config") }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Rotary encoder enabled",
|
||||
checked = cannedMessageInput.rotary1Enabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = {
|
||||
cannedMessageInput = cannedMessageInput.copy { rotary1Enabled = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "GPIO pin for rotary encoder A port",
|
||||
value = cannedMessageInput.inputbrokerPinA,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
cannedMessageInput = cannedMessageInput.copy { inputbrokerPinA = it }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "GPIO pin for rotary encoder B port",
|
||||
value = cannedMessageInput.inputbrokerPinB,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
cannedMessageInput = cannedMessageInput.copy { inputbrokerPinB = it }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "GPIO pin for rotary encoder Press port",
|
||||
value = cannedMessageInput.inputbrokerPinPress,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
cannedMessageInput = cannedMessageInput.copy { inputbrokerPinPress = it }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "Generate input event on Press",
|
||||
enabled = connected,
|
||||
items = ModuleConfig.CannedMessageConfig.InputEventChar.values()
|
||||
.filter { it != ModuleConfig.CannedMessageConfig.InputEventChar.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = cannedMessageInput.inputbrokerEventPress,
|
||||
onItemSelected = {
|
||||
cannedMessageInput = cannedMessageInput.copy { inputbrokerEventPress = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "Generate input event on CW",
|
||||
enabled = connected,
|
||||
items = ModuleConfig.CannedMessageConfig.InputEventChar.values()
|
||||
.filter { it != ModuleConfig.CannedMessageConfig.InputEventChar.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = cannedMessageInput.inputbrokerEventCw,
|
||||
onItemSelected = {
|
||||
cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCw = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "Generate input event on CCW",
|
||||
enabled = connected,
|
||||
items = ModuleConfig.CannedMessageConfig.InputEventChar.values()
|
||||
.filter { it != ModuleConfig.CannedMessageConfig.InputEventChar.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = cannedMessageInput.inputbrokerEventCcw,
|
||||
onItemSelected = {
|
||||
cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCcw = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Up/Down/Select input enabled",
|
||||
checked = cannedMessageInput.updown1Enabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = {
|
||||
cannedMessageInput = cannedMessageInput.copy { updown1Enabled = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = cannedMessageInput != moduleConfig.cannedMessage,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
cannedMessageInput = moduleConfig.cannedMessage
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateCannedMessageConfig { cannedMessageInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "Audio Config") }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "CODEC 2 enabled",
|
||||
checked = audioInput.codec2Enabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { audioInput = audioInput.copy { codec2Enabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "ADC where Microphone is connected",
|
||||
value = audioInput.micChan,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { micChan = it } })
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "DAC where Speaker is connected",
|
||||
value = audioInput.ampPin,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { ampPin = it } })
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "PTT pin",
|
||||
value = audioInput.pttPin,
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { pttPin = it } })
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "CODEC2 sample rate",
|
||||
enabled = connected,
|
||||
items = ModuleConfig.AudioConfig.Audio_Baud.values()
|
||||
.filter { it != ModuleConfig.AudioConfig.Audio_Baud.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = audioInput.bitrate,
|
||||
onItemSelected = { audioInput = audioInput.copy { bitrate = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = audioInput != moduleConfig.audio,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
audioInput = moduleConfig.audio
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateAudioConfig { audioInput }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.geeksville.mesh.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.geeksville.mesh.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun PreferenceScreen(viewModel: UIViewModel = viewModel()) {
|
||||
PreferenceItemList(viewModel)
|
||||
}
|
||||
|
||||
//@Preview(showBackground = true)
|
||||
//@Composable
|
||||
//fun PreferencePreview() {
|
||||
// AppTheme {
|
||||
// PreferenceScreen(viewModel(factory = viewModelFactory { }))
|
||||
// }
|
||||
//}
|
||||
@@ -310,6 +310,14 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||
} else updateNodeInfo()
|
||||
}
|
||||
|
||||
model.moduleConfig.asLiveData().observe(viewLifecycleOwner) {
|
||||
if (!model.isConnected()) {
|
||||
val moduleCount = it.allFields.size
|
||||
if (moduleCount > 0)
|
||||
binding.scanStatusText.text = "Module config ($moduleCount / 8)"
|
||||
} else updateNodeInfo()
|
||||
}
|
||||
|
||||
model.channels.asLiveData().observe(viewLifecycleOwner) {
|
||||
if (!model.isConnected()) it.protobuf.let { ch ->
|
||||
if (!ch.hasLoraConfig() && ch.settingsCount > 0)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
android:background="@color/colorAdvancedBackground">
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/device_config"
|
||||
android:id="@+id/compose_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
@@ -17,9 +17,13 @@
|
||||
android:checked="false"
|
||||
android:title="@string/protocol_stress_test" />
|
||||
<item
|
||||
android:id="@+id/advanced_settings"
|
||||
android:id="@+id/device_settings"
|
||||
app:showAsAction="withText"
|
||||
android:title="@string/advanced_settings" />
|
||||
android:title="@string/device_settings" />
|
||||
<item
|
||||
android:id="@+id/module_settings"
|
||||
app:showAsAction="withText"
|
||||
android:title="@string/module_settings" />
|
||||
<item
|
||||
android:id="@+id/save_messages_csv"
|
||||
app:showAsAction="withText"
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
<string name="ls_sleep_secs">Período de reposo del dispositivo (en segundos)</string>
|
||||
<string name="meshtastic_messages_notifications">Notificaciones de mensajes</string>
|
||||
<string name="protocol_stress_test">Protocolo de prueba de esfuerzo</string>
|
||||
<string name="advanced_settings">Configuración avanzada</string>
|
||||
<string name="firmware_too_old">Es necesario actualizar el firmware</string>
|
||||
<string name="okay">Vale</string>
|
||||
<string name="must_set_region">¡Debe establecer una región!</string>
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
<string name="ls_sleep_secs">Eszköz alvásának gyakorisága (másodpercben)</string>
|
||||
<string name="meshtastic_messages_notifications">Értesítések az üzenetekről</string>
|
||||
<string name="protocol_stress_test">Protokoll stressz teszt</string>
|
||||
<string name="advanced_settings">Haladó beállítások</string>
|
||||
<string name="firmware_too_old">Firmware frissítés szükséges</string>
|
||||
<string name="firmware_old">A rádió firmware túl régi ahhoz, hogy a programmal kommunikálni tudjon, kérem menjen a beállítások oldalra és válassza a "firmware frissítés" gombot. További tudnivalókat a <a href="https://github.com/meshtastic/Meshtastic-device#firmware-installation">firmware frissítés leírásában</a> talál, a Github-on.</string>
|
||||
<string name="okay">OK</string>
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
<string name="ls_sleep_secs">기기가 절전모드 들어가기까지의 시간 (in seconds)</string>
|
||||
<string name="meshtastic_messages_notifications">메시지 알림</string>
|
||||
<string name="protocol_stress_test">프로토콜 스트레스 테스트</string>
|
||||
<string name="advanced_settings">고급 설정</string>
|
||||
<string name="firmware_too_old">펌웨어 업데이트 필요</string>
|
||||
<string name="firmware_old">이 기기의 펌웨어가 매우 오래되어 이 앱과 호환되지않습니다, 설정에서 "펌웨어 업데이트"를 선택하여주세요. 더 자세한정보는 깃허브의 <a href="https://github.com/meshtastic/Meshtastic-device#firmware-installation"> </a> 에서 펌웨어 업데이트 가이드를 참고해주세요..</string>
|
||||
<string name="okay">확인</string>
|
||||
|
||||
@@ -77,7 +77,6 @@
|
||||
<string name="ls_sleep_secs">Okres uśpienia urządzenia (w sekundach)</string>
|
||||
<string name="meshtastic_messages_notifications">Powiadomienia o wiadomościach</string>
|
||||
<string name="protocol_stress_test">Protokół testu warunków skrajnych</string>
|
||||
<string name="advanced_settings">Zaawansowane ustawienia</string>
|
||||
<string name="firmware_too_old">Wymagana aktualizacja oprogramowania układowego</string>
|
||||
<string name="firmware_old">Oprogramowanie układowe radia jest zbyt stare, aby rozmawiać z tą aplikacją, przejdź do panelu ustawień i wybierz „Aktualizuj oprogramowanie układowe”. Aby uzyskać więcej informacji na ten temat, zobacz <a href="https://github.com/meshtastic/Meshtastic-device#firmware-installation">nasz przewodnik instalacji oprogramowania układowego</a> na Github.</string>
|
||||
<string name="okay">OK</string>
|
||||
|
||||
@@ -77,7 +77,6 @@
|
||||
<string name="ls_sleep_secs">Intervalo de suspensão (sleep) (em segundos)</string>
|
||||
<string name="meshtastic_messages_notifications">Notificações sobre mensagens</string>
|
||||
<string name="protocol_stress_test">Stress test do protocolo</string>
|
||||
<string name="advanced_settings">Configurações avançadas</string>
|
||||
<string name="firmware_too_old">Atualização do firmware necessária</string>
|
||||
<string name="firmware_old">Versão de firmware do rádio muito antiga para comunicar com este aplicativo, favor acessar opção "Atualizar firmware" nas Configurações. Para mais informações consultar <a href="https://github.com/meshtastic/Meshtastic-device#firmware-installation">Nosso guia de instalação de firmware</a> no Github.</string>
|
||||
<string name="okay">Okay</string>
|
||||
|
||||
@@ -77,7 +77,6 @@
|
||||
<string name="ls_sleep_secs">Intervalo de suspensão (sleep) (segundos)</string>
|
||||
<string name="meshtastic_messages_notifications">Notificações sobre mensagens</string>
|
||||
<string name="protocol_stress_test">Stress test do protocolo</string>
|
||||
<string name="advanced_settings">Configurações avançadas</string>
|
||||
<string name="firmware_too_old">Atualização do firmware necessária</string>
|
||||
<string name="firmware_old">Versão de firmware do rádio muito antiga para comunicar com este aplicativo, favor acessar opção "Atualizar firmware" nas Configurações. Para mais informações consultar <a href="https://github.com/meshtastic/Meshtastic-device#firmware-installation">Nosso guia de instalação de firmware</a> no Github.</string>
|
||||
<string name="okay">Okay</string>
|
||||
|
||||
@@ -77,7 +77,6 @@
|
||||
<string name="ls_sleep_secs">Interval uspávania zariadenia (v sekundách)</string>
|
||||
<string name="meshtastic_messages_notifications">Upozornenia na správy</string>
|
||||
<string name="protocol_stress_test">Stres test protokolu</string>
|
||||
<string name="advanced_settings">Rozšírené nastavenia</string>
|
||||
<string name="firmware_too_old">Nutná aktualizácia firmvéru vysielača</string>
|
||||
<string name="okay">Ok</string>
|
||||
<string name="must_set_region">Musíte nastaviť región!</string>
|
||||
|
||||
@@ -77,7 +77,6 @@
|
||||
<string name="ls_sleep_secs">设备休眠时间(以秒为单位)</string>
|
||||
<string name="meshtastic_messages_notifications">关于消息的通知</string>
|
||||
<string name="protocol_stress_test">协议压力测试</string>
|
||||
<string name="advanced_settings">高级设置</string>
|
||||
<string name="firmware_too_old">需要固件更新</string>
|
||||
<string name="firmware_old">固件太旧,无法与此应用程序对话,请转到设置窗头并选择"升级固件". 有关这方面的更多信息,请参阅<a href="https://github.com/meshtastic/Meshtastic-device#firmware-installation">固件安装指南</a> 在GitHub</string>
|
||||
<string name="okay">好的</string>
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
<string name="ls_sleep_secs">Device sleep period (in seconds)</string>
|
||||
<string name="meshtastic_messages_notifications">Notifications about messages</string>
|
||||
<string name="protocol_stress_test">Protocol stress test</string>
|
||||
<string name="advanced_settings">Advanced settings</string>
|
||||
<string name="firmware_too_old">Firmware update required</string>
|
||||
<string name="firmware_old">The radio firmware is too old to talk to this application, please go to the settings pane and choose "Update Firmware". For more information on this see <a href="https://github.com/meshtastic/Meshtastic-device#firmware-installation">our Firmware Installation guide</a> on Github.</string>
|
||||
<string name="okay">Okay</string>
|
||||
@@ -163,4 +162,6 @@
|
||||
<string name="start_download">Start Download</string>
|
||||
<string name="request_position">Request position</string>
|
||||
<string name="close">Close</string>
|
||||
<string name="device_settings">Device settings</string>
|
||||
<string name="module_settings">Module settings</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user