feat: add remote node configuration (#626)

This commit is contained in:
Andre K 2023-04-22 12:06:25 -03:00 committed by GitHub
parent ec3a046fb6
commit 85e62eaab4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 523 additions and 267 deletions

View file

@ -24,6 +24,7 @@ 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.MeshProtos.User
import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.repository.datastore.ChannelSetRepository
import com.geeksville.mesh.repository.datastore.LocalConfigRepository
@ -34,7 +35,6 @@ import com.geeksville.mesh.util.positionToMeter
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -45,7 +45,6 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import org.osmdroid.bonuspack.kml.KmlDocument
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.FolderOverlay
@ -120,6 +119,10 @@ class UIViewModel @Inject constructor(
private val _ourNodeInfo = MutableStateFlow<NodeInfo?>(null)
val ourNodeInfo: StateFlow<NodeInfo?> = _ourNodeInfo
private val requestId = MutableStateFlow<Int?>(null)
private val _packetResponse = MutableStateFlow<MeshLog?>(null)
val packetResponse: StateFlow<MeshLog?> = _packetResponse
init {
viewModelScope.launch {
meshLogRepository.getAllLogs().collect { logs ->
@ -148,6 +151,13 @@ class UIViewModel @Inject constructor(
combine(nodeDB.nodes.asFlow(), nodeDB.myId.asFlow()) { nodes, id -> nodes[id] }.onEach {
_ourNodeInfo.value = it
}.launchIn(viewModelScope)
combine(_meshLog, requestId) { packet, requestId ->
requestId?.run { packet.lastOrNull { it.meshPacket?.decoded?.requestId == requestId } }
}.onEach {
_packetResponse.value = it
}.launchIn(viewModelScope)
debug("ViewModel created")
}
@ -174,42 +184,12 @@ class UIViewModel @Inject constructor(
.filterValues { it.data.waypoint!!.expire > System.currentTimeMillis() / 1000 }
}.asLiveData()
private val _packetResponse = MutableStateFlow<MeshLog?>(null)
val packetResponse: StateFlow<MeshLog?> = _packetResponse
/**
* Called immediately after activity observes packetResponse
*/
fun clearPacketResponse() {
_packetResponse.tryEmit(null)
}
/**
* Returns the packet response to a given [packetId] or null after [timeout] milliseconds
*/
private suspend fun getResponseBy(packetId: Int, timeout: Long) = withContext(Dispatchers.IO) {
withTimeoutOrNull(timeout) {
var packet: MeshLog? = null
while (packet == null) {
packet = _meshLog.value.lastOrNull { it.meshPacket?.decoded?.requestId == packetId }
if (packet == null) delay(1000)
}
packet
}
}
fun requestTraceroute(destNum: Int) = viewModelScope.launch {
meshService?.let { service ->
try {
val packetId = service.packetId
val waitFactor = (service.nodes.count { it.isOnline } - 1)
.coerceAtMost(config.lora.hopLimit)
service.requestTraceroute(packetId, destNum)
_packetResponse.emit(getResponseBy(packetId, 20000L * waitFactor))
} catch (ex: RemoteException) {
errormsg("Request traceroute error: ${ex.message}")
}
}
requestId.value = null
_packetResponse.value = null
}
fun generatePacketId(): Int? {
@ -247,6 +227,69 @@ class UIViewModel @Inject constructor(
}
}
private fun request(
destNum: Int,
requestAction: suspend (IMeshService, Int, Int) -> Unit,
errorMessage: String,
configType: Int = 0
) = viewModelScope.launch {
meshService?.let { service ->
val packetId = service.packetId
try {
requestAction(service, packetId, destNum)
requestId.value = packetId
} catch (ex: RemoteException) {
errormsg("$errorMessage: ${ex.message}")
}
}
}
fun getOwner(destNum: Int) = request(
destNum,
{ service, packetId, dest -> service.getRemoteOwner(packetId, dest) },
"Request getOwner error"
)
fun getConfig(destNum: Int, configType: Int) = request(
destNum,
{ service, packetId, dest -> service.getRemoteConfig(packetId, dest, configType) },
"Request getConfig error",
configType
)
fun getModuleConfig(destNum: Int, configType: Int) = request(
destNum,
{ service, packetId, dest -> service.getModuleConfig(packetId, dest, configType) },
"Request getModuleConfig error",
configType
)
fun setRingtone(destNum: Int, ringtone: String) {
meshService?.setRingtone(destNum, ringtone)
}
fun getRingtone(destNum: Int) = request(
destNum,
{ service, packetId, dest -> service.getRingtone(packetId, dest) },
"Request getRingtone error"
)
fun setCannedMessages(destNum: Int, messages: String) {
meshService?.setCannedMessages(destNum, messages)
}
fun getCannedMessages(destNum: Int) = request(
destNum,
{ service, packetId, dest -> service.getCannedMessages(packetId, dest) },
"Request getCannedMessages error"
)
fun requestTraceroute(destNum: Int) = request(
destNum,
{ service, packetId, dest -> service.requestTraceroute(packetId, dest) },
"Request traceroute error"
)
fun requestPosition(destNum: Int, position: Position = Position(0.0, 0.0, 0)) {
try {
meshService?.requestPosition(destNum, position)
@ -325,10 +368,6 @@ class UIViewModel @Inject constructor(
@Suppress("MemberVisibilityCanBePrivate")
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)
private val _myNodeInfo = MutableLiveData<MyNodeInfo?>()
val myNodeInfo: LiveData<MyNodeInfo?> get() = _myNodeInfo
@ -361,93 +400,22 @@ class UIViewModel @Inject constructor(
}
}
inline fun updateDeviceConfig(crossinline body: (Config.DeviceConfig) -> Config.DeviceConfig) {
val data = body(config.device)
setConfig(config { device = data })
}
inline fun updatePositionConfig(crossinline body: (Config.PositionConfig) -> Config.PositionConfig) {
val data = body(config.position)
setConfig(config { position = data })
}
inline fun updatePowerConfig(crossinline body: (Config.PowerConfig) -> Config.PowerConfig) {
val data = body(config.power)
setConfig(config { power = data })
}
inline fun updateNetworkConfig(crossinline body: (Config.NetworkConfig) -> Config.NetworkConfig) {
val data = body(config.network)
setConfig(config { network = data })
}
inline fun updateDisplayConfig(crossinline body: (Config.DisplayConfig) -> Config.DisplayConfig) {
val data = body(config.display)
setConfig(config { display = data })
}
inline fun updateLoraConfig(crossinline body: (Config.LoRaConfig) -> Config.LoRaConfig) {
private inline fun updateLoraConfig(crossinline body: (Config.LoRaConfig) -> Config.LoRaConfig) {
val data = body(config.lora)
setConfig(config { lora = data })
}
inline fun updateBluetoothConfig(crossinline body: (Config.BluetoothConfig) -> Config.BluetoothConfig) {
val data = body(config.bluetooth)
setConfig(config { bluetooth = data })
}
// Set the radio config (also updates our saved copy in preferences)
fun setConfig(config: Config) {
meshService?.setConfig(config.toByteArray())
}
inline fun updateMQTTConfig(crossinline body: (ModuleConfig.MQTTConfig) -> ModuleConfig.MQTTConfig) {
val data = body(module.mqtt)
setModuleConfig(moduleConfig { mqtt = data })
fun setRemoteConfig(destNum: Int, config: Config) {
meshService?.setRemoteConfig(destNum, config.toByteArray())
}
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 })
}
inline fun updateRemoteHardwareConfig(crossinline body: (ModuleConfig.RemoteHardwareConfig) -> ModuleConfig.RemoteHardwareConfig) {
val data = body(module.remoteHardware)
setModuleConfig(moduleConfig { remoteHardware = data })
}
fun setModuleConfig(config: ModuleConfig) {
meshService?.setModuleConfig(config.toByteArray())
fun setModuleConfig(destNum: Int, config: ModuleConfig) {
meshService?.setModuleConfig(destNum, config.toByteArray())
}
/// Convert the channels array to and from [AppOnlyProtos.ChannelSet]
@ -518,6 +486,15 @@ class UIViewModel @Inject constructor(
}
}
fun setRemoteOwner(destNum: Int, user: User) {
try {
// Note: we use ?. here because we might be running in the emulator
meshService?.setRemoteOwner(destNum, user.toByteArray())
} catch (ex: RemoteException) {
errormsg("Can't set username on device, is device offline? ${ex.message}")
}
}
val adminChannelIndex: Int
get() = channelSet.settingsList.map { it.name.lowercase() }.indexOf("admin")
@ -584,7 +561,7 @@ class UIViewModel @Inject constructor(
meshLogRepository.getAllLogsInReceiveOrder(Int.MAX_VALUE).first().forEach { packet ->
// If we get a NodeInfo packet, use it to update our position data (if valid)
packet.nodeInfo?.let { nodeInfo ->
positionToPos.invoke(nodeInfo.position)?.let { _ ->
positionToPos.invoke(nodeInfo.position)?.let {
nodePositions[nodeInfo.num] = nodeInfo.position
}
}
@ -592,7 +569,7 @@ class UIViewModel @Inject constructor(
packet.meshPacket?.let { proto ->
// If the packet contains position data then use it to update, if valid
packet.position?.let { position ->
positionToPos.invoke(position)?.let { _ ->
positionToPos.invoke(position)?.let {
nodePositions[proto.from] = position
}
}