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.lifecycle:lifecycle-livedata-ktx:2.4.1'
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
implementation "com.google.dagger:hilt-android:$hilt_version"
|
implementation "com.google.dagger:hilt-android:$hilt_version"
|
||||||
|
implementation "androidx.datastore:datastore:$datastore_version"
|
||||||
kapt "androidx.room:room-compiler:$room_version"
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
kapt "com.google.dagger:hilt-compiler:$hilt_version"
|
kapt "com.google.dagger:hilt-compiler:$hilt_version"
|
||||||
|
|
||||||
|
|
|
@ -655,7 +655,7 @@ class MainActivity : BaseActivity(), Logging,
|
||||||
else {
|
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
|
// 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)))
|
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.*
|
||||||
import com.geeksville.mesh.database.PacketRepository
|
import com.geeksville.mesh.database.PacketRepository
|
||||||
import com.geeksville.mesh.database.entity.Packet
|
import com.geeksville.mesh.database.entity.Packet
|
||||||
|
import com.geeksville.mesh.repository.datastore.LocalConfigRepository
|
||||||
import com.geeksville.mesh.service.MeshService
|
import com.geeksville.mesh.service.MeshService
|
||||||
import com.geeksville.mesh.util.positionToMeter
|
import com.geeksville.mesh.util.positionToMeter
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
@ -59,6 +60,7 @@ fun getInitials(nameIn: String): String {
|
||||||
class UIViewModel @Inject constructor(
|
class UIViewModel @Inject constructor(
|
||||||
private val app: Application,
|
private val app: Application,
|
||||||
private val packetRepository: PacketRepository,
|
private val packetRepository: PacketRepository,
|
||||||
|
private val localConfigRepository: LocalConfigRepository,
|
||||||
private val preferences: SharedPreferences
|
private val preferences: SharedPreferences
|
||||||
) : ViewModel(), Logging {
|
) : ViewModel(), Logging {
|
||||||
|
|
||||||
|
@ -67,8 +69,15 @@ class UIViewModel @Inject constructor(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
packetRepository.getAllPackets().collect { packets ->
|
launch {
|
||||||
_allPacketState.value = packets
|
packetRepository.getAllPackets().collect { packets ->
|
||||||
|
_allPacketState.value = packets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
localConfigRepository.localConfigFlow.collect { config ->
|
||||||
|
_localConfig.postValue(config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug("ViewModel created")
|
debug("ViewModel created")
|
||||||
|
@ -238,18 +247,6 @@ class UIViewModel @Inject constructor(
|
||||||
private fun setDeviceConfig(config: ConfigProtos.Config) {
|
private fun setDeviceConfig(config: ConfigProtos.Config) {
|
||||||
debug("Setting new radio config!")
|
debug("Setting new radio config!")
|
||||||
meshService?.deviceConfig = config.toByteArray()
|
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) {
|
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.PacketRepository
|
||||||
import com.geeksville.mesh.database.entity.Packet
|
import com.geeksville.mesh.database.entity.Packet
|
||||||
import com.geeksville.mesh.model.DeviceVersion
|
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.location.LocationRepository
|
||||||
import com.geeksville.mesh.repository.radio.BluetoothInterface
|
import com.geeksville.mesh.repository.radio.BluetoothInterface
|
||||||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||||
|
@ -57,6 +58,9 @@ class MeshService : Service(), Logging {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var locationRepository: LocationRepository
|
lateinit var locationRepository: LocationRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var localConfigRepository: LocalConfigRepository
|
||||||
|
|
||||||
companion object : Logging {
|
companion object : Logging {
|
||||||
|
|
||||||
/// Intents broadcast by MeshService
|
/// Intents broadcast by MeshService
|
||||||
|
@ -242,6 +246,11 @@ class MeshService : Service(), Logging {
|
||||||
serviceScope.handledLaunch {
|
serviceScope.handledLaunch {
|
||||||
radioInterfaceService.receivedData.collect(::onReceiveFromRadio)
|
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
|
// the rest of our init will happen once we are in radioConnection.onServiceConnected
|
||||||
}
|
}
|
||||||
|
@ -498,7 +507,7 @@ class MeshService : Service(), Logging {
|
||||||
newPrefs.regionValue = curRegionValue
|
newPrefs.regionValue = curRegionValue
|
||||||
|
|
||||||
newConfig.lora = newPrefs.build()
|
newConfig.lora = newPrefs.build()
|
||||||
sendDeviceConfig(newConfig.build())
|
if (localConfig.lora != newConfig.lora) sendDeviceConfig(newConfig.build())
|
||||||
|
|
||||||
channels = fixupChannelList(asChannels)
|
channels = fixupChannelList(asChannels)
|
||||||
}
|
}
|
||||||
|
@ -691,7 +700,7 @@ class MeshService : Service(), Logging {
|
||||||
if (u.time == 0 && packet.rxTime != 0)
|
if (u.time == 0 && packet.rxTime != 0)
|
||||||
u = u.toBuilder().setTime(packet.rxTime).build()
|
u = u.toBuilder().setTime(packet.rxTime).build()
|
||||||
handleReceivedTelemetry(packet.from, u, dataPacket.time)
|
handleReceivedTelemetry(packet.from, u, dataPacket.time)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new style routing info
|
// Handle new style routing info
|
||||||
Portnums.PortNum.ROUTING_APP_VALUE -> {
|
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()
|
private fun currentSecond() = (System.currentTimeMillis() / 1000).toInt()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1261,7 +1276,9 @@ class MeshService : Service(), Logging {
|
||||||
regenMyNodeInfo()
|
regenMyNodeInfo()
|
||||||
|
|
||||||
// We'll need to get a new set of channels and settings now
|
// 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
|
// prefill the channel array with null channels
|
||||||
channels = fixupChannelList(listOf<ChannelProtos.Channel>())
|
channels = fixupChannelList(listOf<ChannelProtos.Channel>())
|
||||||
|
@ -1476,19 +1493,6 @@ class MeshService : Service(), Logging {
|
||||||
setLocalConfig(c)
|
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
|
* Set our owner with either the new or old API
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -5,6 +5,7 @@ buildscript {
|
||||||
ext.coroutines_version = '1.6.0'
|
ext.coroutines_version = '1.6.0'
|
||||||
ext.room_version = '2.4.2'
|
ext.room_version = '2.4.2'
|
||||||
ext.hilt_version = '2.40.5'
|
ext.hilt_version = '2.40.5'
|
||||||
|
ext.datastore_version = '1.0.0'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
|
|
Ładowanie…
Reference in New Issue