fix: add error handling to SDK bridge and radio controller

SdkStateBridge:
- Wrap handleServiceAction in try/catch to prevent bridge death
- Favorite/Ignore/Mute: only apply local state update on admin
  success (eliminates optimistic state inconsistency)
- ImportContact: guard with runCatching, log failures
- Extract dispatchAction for clean separation of concerns

SdkRadioController:
- Wrap sendMessage with try/catch + logging before re-throw
- Wrap sendRemoteAdmin with try/catch + logging before re-throw
- Ensures BLE disconnect errors are visible in logs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
James Rich
2026-05-05 11:56:45 -05:00
parent 296f27dc73
commit 2db6db5ed9
2 changed files with 61 additions and 25 deletions

View File

@@ -108,8 +108,13 @@ class SdkRadioController(
want_response = false,
),
)
c.send(meshPacket)
serviceRepository.emitMeshActivity(MeshActivity.Send)
try {
c.send(meshPacket)
serviceRepository.emitMeshActivity(MeshActivity.Send)
} catch (e: Exception) {
Logger.e(e) { "sendMessage failed" }
throw e
}
}
// ── Node operations ─────────────────────────────────────────────────────
@@ -462,16 +467,21 @@ class SdkRadioController(
wantResponse: Boolean = false,
) {
val payload = AdminMessage.ADAPTER.encode(adminMsg).toByteString()
c.send(
MeshPacket(
to = destNum,
want_ack = true,
decoded = Data(
portnum = PortNum.ADMIN_APP,
payload = payload,
want_response = wantResponse,
try {
c.send(
MeshPacket(
to = destNum,
want_ack = true,
decoded = Data(
portnum = PortNum.ADMIN_APP,
payload = payload,
want_response = wantResponse,
),
),
),
)
)
} catch (e: Exception) {
Logger.e(e) { "sendRemoteAdmin to $destNum failed" }
throw e
}
}
}

View File

@@ -184,25 +184,47 @@ class SdkStateBridge(
return
}
try {
dispatchAction(client, action)
} catch (e: Exception) {
Logger.e(e) { "[SdkBridge] ServiceAction ${action::class.simpleName} failed" }
if (action is ServiceAction.SendContact) action.result.complete(false)
}
}
@Suppress("CyclomaticComplexMethod")
private suspend fun dispatchAction(client: org.meshtastic.sdk.RadioClient, action: ServiceAction) {
when (action) {
is ServiceAction.Favorite -> {
val node = action.node
client.admin.setFavorite(NodeId(node.num), !node.isFavorite)
nodeRepository.updateNode(node.num) { it.copy(isFavorite = !node.isFavorite) }
val result = runCatching { client.admin.setFavorite(NodeId(node.num), !node.isFavorite) }
if (result.isSuccess) {
nodeRepository.updateNode(node.num) { it.copy(isFavorite = !node.isFavorite) }
} else {
Logger.w(result.exceptionOrNull()) { "[SdkBridge] setFavorite failed for ${node.num}" }
}
}
is ServiceAction.Ignore -> {
val node = action.node
val newIgnored = !node.isIgnored
client.admin.setIgnored(NodeId(node.num), newIgnored)
nodeRepository.updateNode(node.num) { it.copy(isIgnored = newIgnored) }
packetRepository.value.updateFilteredBySender(node.user.id, newIgnored)
val result = runCatching { client.admin.setIgnored(NodeId(node.num), newIgnored) }
if (result.isSuccess) {
nodeRepository.updateNode(node.num) { it.copy(isIgnored = newIgnored) }
packetRepository.value.updateFilteredBySender(node.user.id, newIgnored)
} else {
Logger.w(result.exceptionOrNull()) { "[SdkBridge] setIgnored failed for ${node.num}" }
}
}
is ServiceAction.Mute -> {
val node = action.node
client.admin.toggleMuted(NodeId(node.num))
nodeRepository.updateNode(node.num) { it.copy(isMuted = !node.isMuted) }
val result = runCatching { client.admin.toggleMuted(NodeId(node.num)) }
if (result.isSuccess) {
nodeRepository.updateNode(node.num) { it.copy(isMuted = !node.isMuted) }
} else {
Logger.w(result.exceptionOrNull()) { "[SdkBridge] toggleMuted failed for ${node.num}" }
}
}
is ServiceAction.Reaction -> {
@@ -227,12 +249,16 @@ class SdkStateBridge(
is ServiceAction.ImportContact -> {
val verified = action.contact.copy(manually_verified = true)
client.admin.addContact(verified)
nodeRepository.handleReceivedUser(
verified.node_num,
verified.user ?: User(),
manuallyVerified = true,
)
val result = runCatching { client.admin.addContact(verified) }
if (result.isSuccess) {
nodeRepository.handleReceivedUser(
verified.node_num,
verified.user ?: User(),
manuallyVerified = true,
)
} else {
Logger.w(result.exceptionOrNull()) { "[SdkBridge] importContact failed" }
}
}
is ServiceAction.SendContact -> {