Skip to content

Commit

Permalink
feat: add feedback for configuration changes
Browse files Browse the repository at this point in the history
  • Loading branch information
andrekir committed Oct 3, 2023
1 parent 2dd0e1f commit 7c30d86
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 141 deletions.
8 changes: 4 additions & 4 deletions app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ interface IMeshService {
*/
void setOwner(in MeshUser user);

void setRemoteOwner(in int destNum, in byte []payload);
void setRemoteOwner(in int requestId, in byte []payload);
void getRemoteOwner(in int requestId, in int destNum);

/// Return my unique user ID string
Expand Down Expand Up @@ -91,11 +91,11 @@ interface IMeshService {
void setConfig(in byte []payload);

/// Set and get a Config protobuf via admin packet
void setRemoteConfig(in int destNum, in byte []payload);
void setRemoteConfig(in int requestId, in int destNum, in byte []payload);
void getRemoteConfig(in int requestId, in int destNum, in int configTypeValue);

/// Set and get a ModuleConfig protobuf via admin packet
void setModuleConfig(in int destNum, in byte []payload);
void setModuleConfig(in int requestId, in int destNum, in byte []payload);
void getModuleConfig(in int requestId, in int destNum, in int moduleConfigTypeValue);

/// Set and get the Ext Notification Ringtone string via admin packet
Expand All @@ -111,7 +111,7 @@ interface IMeshService {
void setChannel(in byte []payload);

/// Set and get a Channel protobuf via admin packet
void setRemoteChannel(in int destNum, in byte []payload);
void setRemoteChannel(in int requestId, in int destNum, in byte []payload);
void getRemoteChannel(in int requestId, in int destNum, in int channelIndex);

/// Send beginEditSettings admin packet to nodeNum
Expand Down
136 changes: 76 additions & 60 deletions app/src/main/java/com/geeksville/mesh/model/UIState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class UIViewModel @Inject constructor(
private val _ourNodeInfo = MutableStateFlow<NodeInfo?>(null)
val ourNodeInfo: StateFlow<NodeInfo?> = _ourNodeInfo

private val requestId = MutableStateFlow<Int?>(null)
private val requestIds = MutableStateFlow<HashMap<Int, Boolean>>(hashMapOf())
private val _radioConfigState = MutableStateFlow(RadioConfigState())
val radioConfigState: StateFlow<RadioConfigState> = _radioConfigState

Expand Down Expand Up @@ -169,11 +169,11 @@ class UIViewModel @Inject constructor(
}.launchIn(viewModelScope)

viewModelScope.launch {
combine(meshLogRepository.getAllLogs(9), requestId) { list, id ->
list.takeIf { id != null }?.firstOrNull { it.meshPacket?.decoded?.requestId == id }
}.collect(::processPacketResponse)
combine(meshLogRepository.getAllLogs(9), requestIds) { list, ids ->
val unprocessed = ids.filterValues { !it }.keys.ifEmpty { return@combine emptyList() }
list.filter { log -> log.meshPacket?.decoded?.requestId in unprocessed }
}.collect { it.forEach(::processPacketResponse) }
}

debug("ViewModel created")
}

Expand Down Expand Up @@ -255,7 +255,15 @@ class UIViewModel @Inject constructor(
val packetId = service.packetId
try {
requestAction(service, packetId, destNum)
requestId.value = packetId
requestIds.update { it.apply { put(packetId, false) } }
_radioConfigState.update { state ->
if (state.responseState is ResponseState.Loading) {
val total = maxOf(requestIds.value.size, state.responseState.total)
state.copy(responseState = state.responseState.copy(total = total))
} else {
state.copy(responseState = ResponseState.Loading())
}
}
} catch (ex: RemoteException) {
errormsg("$errorMessage: ${ex.message}")
}
Expand All @@ -268,18 +276,44 @@ class UIViewModel @Inject constructor(
"Request getOwner error"
)

private fun setRemoteChannel(destNum: Int, channel: ChannelProtos.Channel) = request(
destNum,
{ service, packetId, dest ->
service.setRemoteChannel(packetId, dest, channel.toByteArray())
},
"Request setRemoteChannel error"
)

fun getChannel(destNum: Int, index: Int) = request(
destNum,
{ service, packetId, dest -> service.getRemoteChannel(packetId, dest, index) },
"Request getChannel error"
)

fun setRemoteConfig(destNum: Int, config: Config) = request(
destNum,
{ service, packetId, dest ->
_radioConfigState.update { it.copy(radioConfig = config) }
service.setRemoteConfig(packetId, dest, config.toByteArray())
},
"Request setConfig error",
)

fun getConfig(destNum: Int, configType: Int) = request(
destNum,
{ service, packetId, dest -> service.getRemoteConfig(packetId, dest, configType) },
"Request getConfig error",
)

fun setModuleConfig(destNum: Int, config: ModuleConfig) = request(
destNum,
{ service, packetId, dest ->
_radioConfigState.update { it.copy(moduleConfig = config) }
service.setModuleConfig(packetId, dest, config.toByteArray())
},
"Request setConfig error",
)

fun getModuleConfig(destNum: Int, configType: Int) = request(
destNum,
{ service, packetId, dest -> service.getModuleConfig(packetId, dest, configType) },
Expand Down Expand Up @@ -472,16 +506,6 @@ class UIViewModel @Inject constructor(
meshService?.setConfig(config.toByteArray())
}

fun setRemoteConfig(destNum: Int, config: Config) {
_radioConfigState.update { it.copy(radioConfig = config) }
meshService?.setRemoteConfig(destNum, config.toByteArray())
}

fun setModuleConfig(destNum: Int, config: ModuleConfig) {
_radioConfigState.update { it.copy(moduleConfig = config) }
meshService?.setModuleConfig(destNum, config.toByteArray())
}

fun setModuleConfig(config: ModuleConfig) {
setModuleConfig(myNodeNum ?: return, config)
}
Expand Down Expand Up @@ -515,6 +539,7 @@ class UIViewModel @Inject constructor(
if (destNum == myNodeNum) viewModelScope.launch {
radioConfigRepository.replaceAllSettings(new)
}
_radioConfigState.update { it.copy(channelList = new) }
}

private fun updateChannels(
Expand Down Expand Up @@ -542,14 +567,6 @@ class UIViewModel @Inject constructor(
this._channelSet = channelSet.protobuf
}

private fun setRemoteChannel(destNum: Int, channel: ChannelProtos.Channel) {
try {
meshService?.setRemoteChannel(destNum, channel.toByteArray())
} catch (ex: RemoteException) {
errormsg("Can't set channel on radio ${ex.message}")
}
}

val provideLocation = object : MutableLiveData<Boolean>(preferences.getBoolean("provide-location", false)) {
override fun setValue(value: Boolean) {
super.setValue(value)
Expand All @@ -564,15 +581,14 @@ class UIViewModel @Inject constructor(
setRemoteOwner(myNodeNum ?: return, user)
}

fun setRemoteOwner(destNum: Int, user: User) {
try {
// Note: we use ?. here because we might be running in the emulator
meshService?.setRemoteOwner(destNum, user.toByteArray())
fun setRemoteOwner(destNum: Int, user: User) = request(
destNum,
{ service, packetId, _ ->
_radioConfigState.update { it.copy(userConfig = user) }
} catch (ex: RemoteException) {
errormsg("Can't set username on device, is device offline? ${ex.message}")
}
}
service.setRemoteOwner(packetId, user.toByteArray())
},
"Request setOwner error",
)

val adminChannelIndex: Int /** matches [MeshService.adminChannelIndex] **/
get() = channelSet.settingsList.indexOfFirst { it.name.equals("admin", ignoreCase = true) }
Expand Down Expand Up @@ -723,6 +739,7 @@ class UIViewModel @Inject constructor(
message.writeTo(outputStream)
}
}
setResponseStateSuccess()
} catch (ex: Exception) {
val error = "${ex.javaClass.simpleName}: ${ex.message}"
errormsg("Can't write file error: ${ex.message}")
Expand Down Expand Up @@ -763,6 +780,7 @@ class UIViewModel @Inject constructor(
setModuleConfig(moduleConfig { audio = it.audio })
setModuleConfig(moduleConfig { remoteHardware = it.remoteHardware })
}
setResponseStateSuccess()
// meshService?.commitEditSettings()
}

Expand Down Expand Up @@ -806,17 +824,20 @@ class UIViewModel @Inject constructor(
}

fun clearPacketResponse() {
requestIds.value = hashMapOf()
_radioConfigState.update { it.copy(responseState = ResponseState.Empty) }
}

fun setResponseStateLoading(route: String) {
_radioConfigState.value = RadioConfigState(
route = route,
responseState = ResponseState.Loading(total = 1),
responseState = ResponseState.Loading(),
)
// channel editor is synchronous, so we don't use requestIds as total
if (route == ConfigRoute.CHANNELS.name) setResponseStateTotal(maxChannels + 1)
}

fun setResponseStateTotal(total: Int) {
private fun setResponseStateTotal(total: Int) {
_radioConfigState.update { state ->
if (state.responseState is ResponseState.Loading) {
state.copy(responseState = state.responseState.copy(total = total))
Expand All @@ -826,6 +847,16 @@ class UIViewModel @Inject constructor(
}
}

private fun setResponseStateSuccess() {
_radioConfigState.update { state ->
if (state.responseState is ResponseState.Loading) {
state.copy(responseState = ResponseState.Success(true))
} else {
state // Return the unchanged state for other response states
}
}
}

private fun setResponseStateError(error: String) {
_radioConfigState.update { it.copy(responseState = ResponseState.Error(error)) }
}
Expand All @@ -841,14 +872,6 @@ class UIViewModel @Inject constructor(
}
}

fun clearRemoteChannelList() {
_radioConfigState.update { it.copy(channelList = emptyList()) }
}

fun setRemoteChannelList(list: List<ChannelSettings>) {
_radioConfigState.update { it.copy(channelList = list) }
}

private val _tracerouteResponse = MutableLiveData<String?>(null)
val tracerouteResponse: LiveData<String?> get() = _tracerouteResponse

Expand All @@ -859,8 +882,7 @@ class UIViewModel @Inject constructor(
private fun processPacketResponse(log: MeshLog?) {
val packet = log?.meshPacket ?: return
val data = packet.decoded
val fromStr = packet.from.toUInt()
requestId.value = null
requestIds.update { it.apply { put(data.requestId, true) } }

if (data?.portnumValue == Portnums.PortNum.TRACEROUTE_APP_VALUE) {
val parsed = MeshProtos.RouteDiscovery.parseFrom(data.payload)
Expand All @@ -874,49 +896,45 @@ class UIViewModel @Inject constructor(
}
}
val destNum = destNode.value?.num ?: return
val destStr = destNum.toUInt()
val debugMsg = "requestId: ${data.requestId.toUInt()} to: ${destNum.toUInt()} received %s from: ${packet.from.toUInt()}"

if (data?.portnumValue == Portnums.PortNum.ROUTING_APP_VALUE) {
val parsed = MeshProtos.Routing.parseFrom(data.payload)
debug("packet for destNum $destStr received ${parsed.errorReason} from $fromStr")
debug(debugMsg.format(parsed.errorReason.name))
if (parsed.errorReason != MeshProtos.Routing.Error.NONE) {
setResponseStateError(parsed.errorReason.name)
} else if (packet.from == destNum) {
_radioConfigState.update { it.copy(responseState = ResponseState.Success(true)) }
if (requestIds.value.filterValues { !it }.isEmpty()) setResponseStateSuccess()
else incrementCompleted()
}
}
if (data?.portnumValue == Portnums.PortNum.ADMIN_APP_VALUE) {
val parsed = AdminProtos.AdminMessage.parseFrom(data.payload)
debug("packet for destNum $destStr received ${parsed.payloadVariantCase} from $fromStr")
debug(debugMsg.format(parsed.payloadVariantCase.name))
if (destNum != packet.from) {
setResponseStateError("Unexpected sender: $fromStr instead of $destStr.")
setResponseStateError("Unexpected sender: ${packet.from.toUInt()} instead of ${destNum.toUInt()}.")
return
}
// check destination: lora config or channel editor
// check if destination is channel editor
val goChannels = radioConfigState.value.route == ConfigRoute.CHANNELS.name
when (parsed.payloadVariantCase) {
AdminProtos.AdminMessage.PayloadVariantCase.GET_CHANNEL_RESPONSE -> {
val response = parsed.getChannelResponse
// Stop once we get to the first disabled entry
if (response.role != ChannelProtos.Channel.Role.DISABLED) {
_radioConfigState.update { state ->
val updatedList = state.channelList.toMutableList().apply {
state.copy(channelList = state.channelList.toMutableList().apply {
add(response.index, response.settings)
}
state.copy(channelList = updatedList)
})
}
incrementCompleted()
if (response.index + 1 < maxChannels && goChannels) {
// Not done yet, request next channel
getChannel(destNum, response.index + 1)
} else {
// Received max channels, get lora config (for default channel names)
getConfig(destNum, AdminProtos.AdminMessage.ConfigType.LORA_CONFIG_VALUE)
}
} else {
// Received last channel, get lora config (for default channel names)
// Received last channel, update total and start channel editor
setResponseStateTotal(response.index + 1)
getConfig(destNum, AdminProtos.AdminMessage.ConfigType.LORA_CONFIG_VALUE)
}
}

Expand Down Expand Up @@ -948,13 +966,11 @@ class UIViewModel @Inject constructor(
it.copy(cannedMessageMessages = parsed.getCannedMessageModuleMessagesResponse)
}
incrementCompleted()
getModuleConfig(destNum, AdminProtos.AdminMessage.ModuleConfigType.CANNEDMSG_CONFIG_VALUE)
}

AdminProtos.AdminMessage.PayloadVariantCase.GET_RINGTONE_RESPONSE -> {
_radioConfigState.update { it.copy(ringtone = parsed.getRingtoneResponse) }
incrementCompleted()
getModuleConfig(destNum, AdminProtos.AdminMessage.ModuleConfigType.EXTNOTIF_CONFIG_VALUE)
}

else -> TODO()
Expand Down
Loading

0 comments on commit 7c30d86

Please sign in to comment.