sforkowany z mirror/meshtastic-android
create LocalConfig DataStore
rodzic
42755e350e
commit
54f6112908
|
@ -136,6 +136,7 @@ dependencies {
|
|||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
implementation "com.google.dagger:hilt-android:$hilt_version"
|
||||
implementation "androidx.datastore:datastore:$datastore_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
kapt "com.google.dagger:hilt-compiler:$hilt_version"
|
||||
|
||||
|
|
|
@ -655,7 +655,7 @@ class MainActivity : BaseActivity(), Logging,
|
|||
else {
|
||||
// If our app is too old/new, we probably don't understand the new DeviceConfig messages, so we don't read them until here
|
||||
|
||||
model.setLocalConfig(LocalOnlyProtos.LocalConfig.parseFrom(service.deviceConfig))
|
||||
// model.setLocalConfig(LocalOnlyProtos.LocalConfig.parseFrom(service.deviceConfig))
|
||||
|
||||
model.setChannels(ChannelSet(AppOnlyProtos.ChannelSet.parseFrom(service.channels)))
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import com.geeksville.android.Logging
|
|||
import com.geeksville.mesh.*
|
||||
import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.repository.datastore.LocalConfigRepository
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.util.positionToMeter
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
@ -59,6 +60,7 @@ fun getInitials(nameIn: String): String {
|
|||
class UIViewModel @Inject constructor(
|
||||
private val app: Application,
|
||||
private val packetRepository: PacketRepository,
|
||||
private val localConfigRepository: LocalConfigRepository,
|
||||
private val preferences: SharedPreferences
|
||||
) : ViewModel(), Logging {
|
||||
|
||||
|
@ -67,8 +69,15 @@ class UIViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
packetRepository.getAllPackets().collect { packets ->
|
||||
_allPacketState.value = packets
|
||||
launch {
|
||||
packetRepository.getAllPackets().collect { packets ->
|
||||
_allPacketState.value = packets
|
||||
}
|
||||
}
|
||||
launch(Dispatchers.IO) {
|
||||
localConfigRepository.localConfigFlow.collect { config ->
|
||||
_localConfig.postValue(config)
|
||||
}
|
||||
}
|
||||
}
|
||||
debug("ViewModel created")
|
||||
|
@ -238,18 +247,6 @@ class UIViewModel @Inject constructor(
|
|||
private fun setDeviceConfig(config: ConfigProtos.Config) {
|
||||
debug("Setting new radio config!")
|
||||
meshService?.deviceConfig = config.toByteArray()
|
||||
|
||||
// Must be done after calling the service, so we will will properly throw if the service failed (and therefore not cache invalid new settings)
|
||||
_localConfig.value?.let { localConfig ->
|
||||
val builder = localConfig.toBuilder()
|
||||
if (config.hasDevice()) builder.device = config.device
|
||||
if (config.hasPosition()) builder.position = config.position
|
||||
if (config.hasPower()) builder.power = config.power
|
||||
if (config.hasWifi()) builder.wifi = config.wifi
|
||||
if (config.hasDisplay()) builder.display = config.display
|
||||
if (config.hasLora()) builder.lora = config.lora
|
||||
_localConfig.value = builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
fun setLocalConfig(localConfig: LocalOnlyProtos.LocalConfig) {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package com.geeksville.mesh.repository.datastore
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
object DataStoreModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideLocalConfigDataStore(@ApplicationContext appContext: Context): DataStore<LocalConfig> {
|
||||
return DataStoreFactory.create(
|
||||
serializer = LocalConfigSerializer,
|
||||
produceFile = { appContext.dataStoreFile("local_config.pb") },
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(
|
||||
produceNewData = { LocalConfig.getDefaultInstance() }
|
||||
),
|
||||
scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package com.geeksville.mesh.repository.datastore
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Class that handles saving and retrieving config settings
|
||||
*/
|
||||
class LocalConfigRepository @Inject constructor(
|
||||
private val localConfigStore: DataStore<LocalConfig>
|
||||
) : Logging {
|
||||
val localConfigFlow: Flow<LocalConfig> = localConfigStore.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(LocalConfig.getDefaultInstance())
|
||||
} else {
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
|
||||
private val _sendDeviceConfigFlow = MutableSharedFlow<ConfigProtos.Config>()
|
||||
val sendDeviceConfigFlow = _sendDeviceConfigFlow.asSharedFlow()
|
||||
|
||||
private suspend fun sendDeviceConfig(config: ConfigProtos.Config) {
|
||||
debug("Sending device config!")
|
||||
_sendDeviceConfigFlow.emit(config)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update LocalConfig and send ConfigProtos.Config Oneof to the radio
|
||||
*/
|
||||
suspend fun setRadioConfig(config: ConfigProtos.Config) {
|
||||
setLocalConfig(config)
|
||||
sendDeviceConfig(config)
|
||||
}
|
||||
|
||||
suspend fun clearLocalConfig() {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().clear().build()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update LocalConfig from each ConfigProtos.Config Oneof
|
||||
*/
|
||||
suspend fun setLocalConfig(config: ConfigProtos.Config) {
|
||||
if (config.hasDevice()) setDeviceConfig(config.device)
|
||||
if (config.hasPosition()) setPositionConfig(config.position)
|
||||
if (config.hasPower()) setPowerConfig(config.power)
|
||||
if (config.hasWifi()) setWifiConfig(config.wifi)
|
||||
if (config.hasDisplay()) setDisplayConfig(config.display)
|
||||
if (config.hasLora()) setLoraConfig(config.lora)
|
||||
}
|
||||
|
||||
private suspend fun setDeviceConfig(config: ConfigProtos.Config.DeviceConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setDevice(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setPositionConfig(config: ConfigProtos.Config.PositionConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setPosition(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setPowerConfig(config: ConfigProtos.Config.PowerConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setPower(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setWifiConfig(config: ConfigProtos.Config.WiFiConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setWifi(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setDisplayConfig(config: ConfigProtos.Config.DisplayConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setDisplay(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setLoraConfig(config: ConfigProtos.Config.LoRaConfig) {
|
||||
localConfigStore.updateData { preference ->
|
||||
preference.toBuilder().setLora(config).build()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchInitialLocalConfig() = localConfigStore.data.first()
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.geeksville.mesh.repository.datastore
|
||||
|
||||
import androidx.datastore.core.CorruptionException
|
||||
import androidx.datastore.core.Serializer
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* Serializer for the [LocalConfig] object defined in localonly.proto.
|
||||
*/
|
||||
object LocalConfigSerializer : Serializer<LocalConfig> {
|
||||
override val defaultValue: LocalConfig = LocalConfig.getDefaultInstance()
|
||||
|
||||
override suspend fun readFrom(input: InputStream): LocalConfig {
|
||||
try {
|
||||
return LocalConfig.parseFrom(input)
|
||||
} catch (exception: InvalidProtocolBufferException) {
|
||||
throw CorruptionException("Cannot read proto.", exception)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun writeTo(t: LocalConfig, output: OutputStream) = t.writeTo(output)
|
||||
}
|
|
@ -18,6 +18,7 @@ import com.geeksville.mesh.android.hasBackgroundPermission
|
|||
import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.repository.datastore.LocalConfigRepository
|
||||
import com.geeksville.mesh.repository.location.LocationRepository
|
||||
import com.geeksville.mesh.repository.radio.BluetoothInterface
|
||||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||
|
@ -57,6 +58,9 @@ class MeshService : Service(), Logging {
|
|||
@Inject
|
||||
lateinit var locationRepository: LocationRepository
|
||||
|
||||
@Inject
|
||||
lateinit var localConfigRepository: LocalConfigRepository
|
||||
|
||||
companion object : Logging {
|
||||
|
||||
/// Intents broadcast by MeshService
|
||||
|
@ -242,6 +246,11 @@ class MeshService : Service(), Logging {
|
|||
serviceScope.handledLaunch {
|
||||
radioInterfaceService.receivedData.collect(::onReceiveFromRadio)
|
||||
}
|
||||
serviceScope.handledLaunch {
|
||||
localConfigRepository.localConfigFlow.collect { config ->
|
||||
localConfig = config
|
||||
}
|
||||
}
|
||||
|
||||
// the rest of our init will happen once we are in radioConnection.onServiceConnected
|
||||
}
|
||||
|
@ -498,7 +507,7 @@ class MeshService : Service(), Logging {
|
|||
newPrefs.regionValue = curRegionValue
|
||||
|
||||
newConfig.lora = newPrefs.build()
|
||||
sendDeviceConfig(newConfig.build())
|
||||
if (localConfig.lora != newConfig.lora) sendDeviceConfig(newConfig.build())
|
||||
|
||||
channels = fixupChannelList(asChannels)
|
||||
}
|
||||
|
@ -691,7 +700,7 @@ class MeshService : Service(), Logging {
|
|||
if (u.time == 0 && packet.rxTime != 0)
|
||||
u = u.toBuilder().setTime(packet.rxTime).build()
|
||||
handleReceivedTelemetry(packet.from, u, dataPacket.time)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle new style routing info
|
||||
Portnums.PortNum.ROUTING_APP_VALUE -> {
|
||||
|
@ -935,6 +944,12 @@ class MeshService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
private fun setLocalConfig (config: ConfigProtos.Config) {
|
||||
serviceScope.handledLaunch {
|
||||
localConfigRepository.setLocalConfig(config)
|
||||
}
|
||||
}
|
||||
|
||||
private fun currentSecond() = (System.currentTimeMillis() / 1000).toInt()
|
||||
|
||||
|
||||
|
@ -1261,7 +1276,9 @@ class MeshService : Service(), Logging {
|
|||
regenMyNodeInfo()
|
||||
|
||||
// We'll need to get a new set of channels and settings now
|
||||
localConfig = LocalOnlyProtos.LocalConfig.getDefaultInstance()
|
||||
serviceScope.handledLaunch {
|
||||
localConfigRepository.clearLocalConfig()
|
||||
}
|
||||
|
||||
// prefill the channel array with null channels
|
||||
channels = fixupChannelList(listOf<ChannelProtos.Channel>())
|
||||
|
@ -1476,19 +1493,6 @@ class MeshService : Service(), Logging {
|
|||
setLocalConfig(c)
|
||||
}
|
||||
|
||||
/** Set our localConfig
|
||||
*/
|
||||
private fun setLocalConfig(config: ConfigProtos.Config) {
|
||||
val builder = localConfig.toBuilder()
|
||||
if (config.hasDevice()) builder.device = config.device
|
||||
if (config.hasPosition()) builder.position = config.position
|
||||
if (config.hasPower()) builder.power = config.power
|
||||
if (config.hasWifi()) builder.wifi = config.wifi
|
||||
if (config.hasDisplay()) builder.display = config.display
|
||||
if (config.hasLora()) builder.lora = config.lora
|
||||
localConfig = builder.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set our owner with either the new or old API
|
||||
*/
|
||||
|
|
|
@ -5,6 +5,7 @@ buildscript {
|
|||
ext.coroutines_version = '1.6.0'
|
||||
ext.room_version = '2.4.2'
|
||||
ext.hilt_version = '2.40.5'
|
||||
ext.datastore_version = '1.0.0'
|
||||
|
||||
repositories {
|
||||
google()
|
||||
|
|
Ładowanie…
Reference in New Issue