diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/HistoryManagerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/HistoryManagerImpl.kt deleted file mode 100644 index aed060531..000000000 --- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/HistoryManagerImpl.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.data.manager - -import co.touchlab.kermit.Logger -import kotlinx.coroutines.CoroutineScope -import org.koin.core.annotation.Named -import org.koin.core.annotation.Single -import org.meshtastic.core.common.util.handledLaunch -import org.meshtastic.core.model.RadioController -import org.meshtastic.core.repository.HistoryManager -import org.meshtastic.core.repository.MeshPrefs -import org.meshtastic.proto.ModuleConfig - -@Single -class HistoryManagerImpl( - private val meshPrefs: MeshPrefs, - private val radioController: RadioController, - @Named("ServiceScope") private val scope: CoroutineScope, -) : HistoryManager { - - companion object { - private const val HISTORY_TAG = "HistoryReplay" - private const val DEFAULT_HISTORY_RETURN_WINDOW_MINUTES = 60 * 24 - private const val DEFAULT_HISTORY_RETURN_MAX_MESSAGES = 100 - private const val NO_DEVICE_SELECTED = "No device selected" - - fun resolveHistoryRequestParameters(window: Int, max: Int): Pair { - val resolvedWindow = if (window > 0) window else DEFAULT_HISTORY_RETURN_WINDOW_MINUTES - val resolvedMax = if (max > 0) max else DEFAULT_HISTORY_RETURN_MAX_MESSAGES - return resolvedWindow to resolvedMax - } - } - - private val logger = Logger.withTag(HISTORY_TAG) - - private fun historyLog(message: String, throwable: Throwable? = null) { - logger.i(throwable) { message } - } - - private fun activeDeviceAddress(): String? = - meshPrefs.deviceAddress.value?.takeIf { !it.equals(NO_DEVICE_SELECTED, ignoreCase = true) && it.isNotBlank() } - - override fun requestHistoryReplay( - trigger: String, - myNodeNum: Int?, - storeForwardConfig: ModuleConfig.StoreForwardConfig?, - transport: String, - ) { - val address = activeDeviceAddress() - if (address == null || myNodeNum == null) { - val reason = if (address == null) "no_addr" else "no_my_node" - historyLog("requestHistory skipped trigger=$trigger reason=$reason") - return - } - - val lastRequest = meshPrefs.getStoreForwardLastRequest(address).value.takeIf { it > 0 } - val (window, max) = - resolveHistoryRequestParameters( - storeForwardConfig?.history_return_window ?: 0, - storeForwardConfig?.history_return_max ?: 0, - ) - - historyLog( - "requestHistory trigger=$trigger transport=$transport addr=$address " + - "since=${lastRequest ?: "all"} window=$window max=$max via=sdk", - ) - - scope.handledLaunch { - val accepted = radioController.requestStoreForwardHistory(since = lastRequest) - if (!accepted) { - logger.w { - "requestHistory failed trigger=$trigger transport=$transport addr=$address since=${lastRequest ?: "all"}" - } - } - } - } - - override fun updateStoreForwardLastRequest(source: String, lastRequest: Int, transport: String) { - if (lastRequest <= 0) return - val address = activeDeviceAddress() ?: return - val current = meshPrefs.getStoreForwardLastRequest(address).value - if (lastRequest != current) { - meshPrefs.setStoreForwardLastRequest(address, lastRequest) - historyLog( - "historyMarker updated source=$source transport=$transport " + - "addr=$address from=$current to=$lastRequest", - ) - } - } -} diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MessagePersistenceHandler.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MessagePersistenceHandler.kt deleted file mode 100644 index 3d4dd73e2..000000000 --- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MessagePersistenceHandler.kt +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.data.manager - -import co.touchlab.kermit.Logger -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import org.koin.core.annotation.Named -import org.koin.core.annotation.Single -import org.meshtastic.core.common.util.handledLaunch -import org.meshtastic.core.common.util.nowMillis -import org.meshtastic.core.model.DataPacket -import org.meshtastic.core.repository.MeshDataHandler -import org.meshtastic.core.repository.MeshServiceNotifications -import org.meshtastic.core.repository.MessageFilter -import org.meshtastic.core.repository.NodeRepository -import org.meshtastic.core.repository.Notification -import org.meshtastic.core.repository.NotificationManager -import org.meshtastic.core.repository.PacketRepository -import org.meshtastic.core.repository.RadioConfigRepository -import org.meshtastic.core.resources.Res -import org.meshtastic.core.resources.critical_alert -import org.meshtastic.core.resources.getStringSuspend -import org.meshtastic.core.resources.unknown_username -import org.meshtastic.core.resources.waypoint_received -import org.meshtastic.proto.PortNum - -/** - * SDK-era implementation of [MeshDataHandler] focused on message persistence and notifications. - * - * The full packet-routing logic (handleReceivedData) is no longer needed — the SDK's packet flow - * is consumed directly by VMs and SdkStateBridge. This class retains only [rememberDataPacket] - * which is called by [StoreForwardPacketHandlerImpl] to persist forwarded messages. - */ -@Single -class MessagePersistenceHandler( - private val nodeRepository: NodeRepository, - private val packetRepository: Lazy, - private val notificationManager: NotificationManager, - private val serviceNotifications: MeshServiceNotifications, - private val radioConfigRepository: RadioConfigRepository, - private val messageFilter: MessageFilter, - @Named("ServiceScope") private val scope: CoroutineScope, -) : MeshDataHandler { - - private val rememberDataType = - setOf( - PortNum.TEXT_MESSAGE_APP.value, - PortNum.ALERT_APP.value, - PortNum.WAYPOINT_APP.value, - PortNum.NODE_STATUS_APP.value, - ) - - override fun handleReceivedData( - packet: org.meshtastic.proto.MeshPacket, - myNodeNum: Int, - logUuid: String?, - logInsertJob: kotlinx.coroutines.Job?, - ) { - // No-op: Incoming packet routing is handled by SdkStateBridge / VM packet observers. - // This method exists only to satisfy the MeshDataHandler interface contract. - } - - override fun rememberDataPacket(dataPacket: DataPacket, myNodeNum: Int, updateNotification: Boolean) { - if (dataPacket.dataType !in rememberDataType) return - val fromLocal = dataPacket.from == DataPacket.LOCAL || dataPacket.from == myNodeNum - val toBroadcast = dataPacket.to == DataPacket.BROADCAST - val contactId = if (fromLocal || toBroadcast) dataPacket.to else dataPacket.from - - val contactKey = "${dataPacket.channel}${DataPacket.nodeNumToId(contactId)}" - - scope.handledLaunch { - packetRepository.value.apply { - val existingPackets = findPacketsWithId(dataPacket.id) - if (existingPackets.isNotEmpty()) { - Logger.d { - "Skipping duplicate packet: packetId=${dataPacket.id} from=${dataPacket.from} " + - "to=${dataPacket.to} contactKey=$contactKey" + - " (already have ${existingPackets.size} packet(s))" - } - return@handledLaunch - } - - val isFiltered = shouldFilterMessage(dataPacket, contactKey) - - insert( - dataPacket, - myNodeNum, - contactKey, - nowMillis, - read = fromLocal || isFiltered, - filtered = isFiltered, - ) - if (!isFiltered) { - handlePacketNotification(dataPacket, contactKey, updateNotification) - } - } - } - } - - @Suppress("ReturnCount") - private suspend fun PacketRepository.shouldFilterMessage(dataPacket: DataPacket, contactKey: String): Boolean { - val isIgnored = nodeRepository.nodeDBbyNum.value[dataPacket.from]?.isIgnored == true - if (isIgnored) return true - - if (dataPacket.dataType != PortNum.TEXT_MESSAGE_APP.value) return false - val isFilteringDisabled = getContactSettings(contactKey).filteringDisabled - return messageFilter.shouldFilter(dataPacket.text.orEmpty(), isFilteringDisabled) - } - - private suspend fun handlePacketNotification( - dataPacket: DataPacket, - contactKey: String, - updateNotification: Boolean, - ) { - val conversationMuted = packetRepository.value.getContactSettings(contactKey).isMuted - val nodeMuted = nodeRepository.nodeDBbyNum.value[dataPacket.from]?.isMuted == true - val isSilent = conversationMuted || nodeMuted - if (dataPacket.dataType == PortNum.ALERT_APP.value && !isSilent) { - scope.launch { - notificationManager.dispatch( - Notification( - title = getSenderName(dataPacket), - message = dataPacket.alert ?: getStringSuspend(Res.string.critical_alert), - category = Notification.Category.Alert, - contactKey = contactKey, - ), - ) - } - } else if (updateNotification && !isSilent) { - scope.handledLaunch { updateNotification(contactKey, dataPacket, isSilent) } - } - } - - private suspend fun getSenderName(packet: DataPacket): String { - if (packet.from == DataPacket.LOCAL) { - return nodeRepository.ourNodeInfo.value?.user?.long_name ?: getStringSuspend(Res.string.unknown_username) - } - return nodeRepository.nodeDBbyNum.value[packet.from]?.user?.long_name ?: getStringSuspend(Res.string.unknown_username) - } - - private suspend fun updateNotification(contactKey: String, dataPacket: DataPacket, isSilent: Boolean) { - when (dataPacket.dataType) { - PortNum.TEXT_MESSAGE_APP.value -> { - val message = dataPacket.text!! - val channelName = - if (dataPacket.to == DataPacket.BROADCAST) { - radioConfigRepository.channelSetFlow.first().settings.getOrNull(dataPacket.channel)?.name - } else { - null - } - serviceNotifications.updateMessageNotification( - contactKey, - getSenderName(dataPacket), - message, - dataPacket.to == DataPacket.BROADCAST, - channelName, - isSilent, - ) - } - - PortNum.WAYPOINT_APP.value -> { - val message = getStringSuspend(Res.string.waypoint_received, dataPacket.waypoint!!.name) - notificationManager.dispatch( - Notification( - title = getSenderName(dataPacket), - message = message, - category = Notification.Category.Message, - contactKey = contactKey, - isSilent = isSilent, - ), - ) - } - - else -> return - } - } -} diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NeighborInfoHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NeighborInfoHandlerImpl.kt deleted file mode 100644 index a74dbd849..000000000 --- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NeighborInfoHandlerImpl.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.data.manager - -import co.touchlab.kermit.Logger -import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.update -import kotlinx.collections.immutable.persistentMapOf -import org.koin.core.annotation.Single -import org.meshtastic.core.common.util.NumberFormatter -import org.meshtastic.core.common.util.nowMillis -import org.meshtastic.core.repository.NeighborInfoHandler -import org.meshtastic.core.repository.NodeRepository -import org.meshtastic.core.repository.ServiceRepository -import org.meshtastic.proto.MeshPacket -import org.meshtastic.proto.NeighborInfo -import org.meshtastic.sdk.NeighborInfo as SdkNeighborInfo - -@Single -class NeighborInfoHandlerImpl( - private val nodeRepository: NodeRepository, - private val serviceRepository: ServiceRepository, -) : NeighborInfoHandler { - - private val startTimes = atomic(persistentMapOf()) - - override var lastNeighborInfo: NeighborInfo? = null - - override fun recordStartTime(requestId: Int) { - startTimes.update { it.put(requestId, nowMillis) } - } - - override fun handleNeighborInfo(packet: MeshPacket) { - val payload = packet.decoded?.payload ?: return - val ni = NeighborInfo.ADAPTER.decode(payload) - - val from = packet.from - if (from == nodeRepository.myNodeNum.value) { - lastNeighborInfo = ni - Logger.d { "Stored last neighbor info from connected radio" } - } - - val requestId = packet.decoded?.request_id ?: 0 - val start = startTimes.value[requestId] - startTimes.update { it.remove(requestId) } - - val formatted = - SdkNeighborInfo - .fromProto( - reportingNode = from, - neighborNodeIds = ni.neighbors.map { it.node_id }, - snrValues = ni.neighbors.map { it.snr }, - ).format { nodeId -> - val user = nodeRepository.getUser(nodeId.raw) - "${user.long_name} (${user.short_name})" - } - - val responseText = - if (start != null) { - val elapsedMs = nowMillis - start - val seconds = elapsedMs / MILLIS_PER_SECOND - Logger.i { "Neighbor info $requestId complete in $seconds s" } - "$formatted\nDuration: ${NumberFormatter.format(seconds, 1)} s" - } else { - formatted - } - - serviceRepository.setNeighborInfoResponse(responseText) - } - - companion object { - private const val MILLIS_PER_SECOND = 1000.0 - } -} diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImpl.kt deleted file mode 100644 index 503a045ad..000000000 --- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImpl.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.data.manager - -import co.touchlab.kermit.Logger -import kotlinx.coroutines.CoroutineScope -import okio.ByteString.Companion.toByteString -import org.koin.core.annotation.Named -import org.koin.core.annotation.Single -import org.meshtastic.core.model.DataPacket -import org.meshtastic.core.repository.HistoryManager -import org.meshtastic.core.repository.MeshDataHandler -import org.meshtastic.core.repository.PacketRepository -import org.meshtastic.core.repository.StoreForwardPacketHandler -import org.meshtastic.proto.MeshPacket -import org.meshtastic.proto.PortNum -import org.meshtastic.proto.StoreAndForward -import kotlin.time.Duration.Companion.milliseconds - -/** - * Implementation of [StoreForwardPacketHandler] that keeps legacy S&F parsing for backward compatibility. - * - * SF++ parsing/status updates are delegated to the SDK and consumed via [org.meshtastic.core.data.radio.SdkStateBridge]. - */ -@Single -class StoreForwardPacketHandlerImpl( - private val packetRepository: Lazy, - private val historyManager: HistoryManager, - private val dataHandler: Lazy, - @Named("ServiceScope") private val scope: CoroutineScope, -) : StoreForwardPacketHandler { - - override fun handleStoreAndForward(packet: MeshPacket, dataPacket: DataPacket, myNodeNum: Int) { - val payload = packet.decoded?.payload ?: return - val u = StoreAndForward.ADAPTER.decode(payload) - handleReceivedStoreAndForward(dataPacket, u, myNodeNum) - } - - private fun handleReceivedStoreAndForward(dataPacket: DataPacket, s: StoreAndForward, myNodeNum: Int) { - val lastRequest = s.history?.last_request ?: 0 - Logger.d { "StoreAndForward from=${dataPacket.from} lastRequest=$lastRequest" } - when { - s.stats != null -> { - val text = s.stats.toString() - val u = - dataPacket.copy( - bytes = text.encodeToByteArray().toByteString(), - dataType = PortNum.TEXT_MESSAGE_APP.value, - ) - dataHandler.value.rememberDataPacket(u, myNodeNum) - } - - s.history != null -> { - val h = s.history!! - val text = - "Total messages: ${h.history_messages}\n" + - "History window: ${h.window.milliseconds.inWholeMinutes} min\n" + - "Last request: ${h.last_request}" - val u = - dataPacket.copy( - bytes = text.encodeToByteArray().toByteString(), - dataType = PortNum.TEXT_MESSAGE_APP.value, - ) - dataHandler.value.rememberDataPacket(u, myNodeNum) - historyManager.updateStoreForwardLastRequest("router_history", h.last_request, "Unknown") - } - - s.heartbeat != null -> { - val hb = s.heartbeat!! - Logger.d { "rxHeartbeat from=${dataPacket.from} period=${hb.period} secondary=${hb.secondary}" } - } - - s.text != null -> { - if (s.rr == StoreAndForward.RequestResponse.ROUTER_TEXT_BROADCAST) { - dataPacket.to = DataPacket.BROADCAST - } - val u = dataPacket.copy(bytes = s.text, dataType = PortNum.TEXT_MESSAGE_APP.value) - dataHandler.value.rememberDataPacket(u, myNodeNum) - } - - else -> {} - } - } -} diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.kt deleted file mode 100644 index 57be6f731..000000000 --- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.kt +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.data.manager - -import co.touchlab.kermit.Logger -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import org.koin.core.annotation.Named -import org.koin.core.annotation.Single -import org.meshtastic.core.common.util.nowSeconds -import org.meshtastic.core.model.DataPacket -import org.meshtastic.core.model.Node -import org.meshtastic.core.model.util.decodeOrNull -import org.meshtastic.core.model.util.toOneLiner -import org.meshtastic.core.repository.NodeRepository -import org.meshtastic.core.repository.Notification -import org.meshtastic.core.repository.NotificationManager -import org.meshtastic.core.repository.TelemetryPacketHandler -import org.meshtastic.core.resources.Res -import org.meshtastic.core.resources.getStringSuspend -import org.meshtastic.core.resources.low_battery_message -import org.meshtastic.core.resources.low_battery_title -import org.meshtastic.proto.MeshPacket -import org.meshtastic.proto.Telemetry -import kotlin.time.Duration.Companion.milliseconds - -/** - * Implementation of [TelemetryPacketHandler] that processes telemetry packets and manages battery-level notifications - * with cooldown logic. - */ -@Single -class TelemetryPacketHandlerImpl( - private val nodeRepository: NodeRepository, - private val notificationManager: NotificationManager, - @Named("ServiceScope") private val scope: CoroutineScope, -) : TelemetryPacketHandler { - - private val batteryMutex = Mutex() - private val batteryPercentCooldowns = mutableMapOf() - - @Suppress("LongMethod", "CyclomaticComplexMethod") - override fun handleTelemetry(packet: MeshPacket, dataPacket: DataPacket, myNodeNum: Int) { - val payload = packet.decoded?.payload ?: return - val t = - (Telemetry.ADAPTER.decodeOrNull(payload, Logger) ?: return).let { - if (it.time == 0) it.copy(time = (dataPacket.time.milliseconds.inWholeSeconds).toInt()) else it - } - Logger.d { "Telemetry from ${packet.from}: ${Telemetry.ADAPTER.toOneLiner(t)}" } - val fromNum = packet.from - val isRemote = (fromNum != myNodeNum) - // Note: Local telemetry notification update was previously handled by - // MeshConnectionManager.updateTelemetry(), now managed via SDK flows. - - nodeRepository.updateNode(fromNum) { node: Node -> - val metrics = t.device_metrics - val environment = t.environment_metrics - val power = t.power_metrics - - var nextNode = node - when { - metrics != null -> { - nextNode = nextNode.copy(deviceMetrics = metrics) - if (fromNum == myNodeNum || (isRemote && node.isFavorite)) { - if ( - (metrics.voltage ?: 0f) > BATTERY_PERCENT_UNSUPPORTED && - (metrics.battery_level ?: 0) <= BATTERY_PERCENT_LOW_THRESHOLD - ) { - scope.launch { - if (shouldBatteryNotificationShow(fromNum, t, myNodeNum)) { - notificationManager.dispatch( - Notification( - title = - getStringSuspend( - Res.string.low_battery_title, - nextNode.user.short_name, - ), - message = - getStringSuspend( - Res.string.low_battery_message, - nextNode.user.long_name, - nextNode.deviceMetrics.battery_level ?: 0, - ), - category = Notification.Category.Battery, - ), - ) - } - } - } else { - scope.launch { - batteryMutex.withLock { - if (batteryPercentCooldowns.containsKey(fromNum)) { - batteryPercentCooldowns.remove(fromNum) - } - } - notificationManager.cancel(nextNode.num) - } - } - } - } - - environment != null -> nextNode = nextNode.copy(environmentMetrics = environment) - - power != null -> nextNode = nextNode.copy(powerMetrics = power) - } - - val telemetryTime = if (t.time != 0) t.time else nextNode.lastHeard - val newLastHeard = maxOf(nextNode.lastHeard, telemetryTime) - nextNode.copy(lastHeard = newLastHeard) - } - } - - @Suppress("ReturnCount") - private suspend fun shouldBatteryNotificationShow(fromNum: Int, t: Telemetry, myNodeNum: Int): Boolean { - val isRemote = (fromNum != myNodeNum) - var shouldDisplay = false - var forceDisplay = false - val metrics = t.device_metrics ?: return false - val batteryLevel = metrics.battery_level ?: 0 - when { - batteryLevel <= BATTERY_PERCENT_CRITICAL_THRESHOLD -> { - shouldDisplay = true - forceDisplay = true - } - - batteryLevel == BATTERY_PERCENT_LOW_THRESHOLD -> shouldDisplay = true - - batteryLevel.mod(BATTERY_PERCENT_LOW_DIVISOR) == 0 && !isRemote -> shouldDisplay = true - - isRemote -> shouldDisplay = true - } - if (shouldDisplay) { - val now = nowSeconds - batteryMutex.withLock { - if (!batteryPercentCooldowns.containsKey(fromNum)) batteryPercentCooldowns[fromNum] = 0L - if ((now - batteryPercentCooldowns[fromNum]!!) >= BATTERY_PERCENT_COOLDOWN_SECONDS || forceDisplay) { - batteryPercentCooldowns[fromNum] = now - return true - } - } - } - return false - } - - companion object { - private const val BATTERY_PERCENT_UNSUPPORTED = 0.0 - private const val BATTERY_PERCENT_LOW_THRESHOLD = 20 - private const val BATTERY_PERCENT_LOW_DIVISOR = 5 - private const val BATTERY_PERCENT_CRITICAL_THRESHOLD = 5 - private const val BATTERY_PERCENT_COOLDOWN_SECONDS = 1500 - } -} diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.kt deleted file mode 100644 index e3668df39..000000000 --- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.data.manager - -import co.touchlab.kermit.Logger -import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.update -import kotlinx.collections.immutable.persistentMapOf -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import org.koin.core.annotation.Named -import org.koin.core.annotation.Single -import org.meshtastic.core.common.util.NumberFormatter -import org.meshtastic.core.common.util.handledLaunch -import org.meshtastic.core.common.util.nowMillis -import org.meshtastic.core.model.fullRouteDiscovery -import org.meshtastic.core.model.getTracerouteResponse -import org.meshtastic.core.model.service.TracerouteResponse -import org.meshtastic.core.repository.NodeRepository -import org.meshtastic.core.repository.ServiceRepository -import org.meshtastic.core.repository.TracerouteHandler -import org.meshtastic.core.repository.TracerouteSnapshotRepository -import org.meshtastic.core.resources.Res -import org.meshtastic.core.resources.getStringSuspend -import org.meshtastic.core.resources.traceroute_route_back_to_us -import org.meshtastic.core.resources.traceroute_route_towards_dest -import org.meshtastic.proto.MeshPacket - -@Single -class TracerouteHandlerImpl( - private val serviceRepository: ServiceRepository, - private val tracerouteSnapshotRepository: TracerouteSnapshotRepository, - private val nodeRepository: NodeRepository, - @Named("ServiceScope") private val scope: CoroutineScope, -) : TracerouteHandler { - - private val startTimes = atomic(persistentMapOf()) - - override fun recordStartTime(requestId: Int) { - startTimes.update { it.put(requestId, nowMillis) } - } - - override fun handleTraceroute(packet: MeshPacket, logUuid: String?, logInsertJob: Job?) { - // Decode the route discovery once — avoids triple protobuf decode - val routeDiscovery = packet.fullRouteDiscovery ?: return - val forwardRoute = routeDiscovery.route - val returnRoute = routeDiscovery.route_back - - // Require both directions for a "full" traceroute response - if (forwardRoute.isEmpty() || returnRoute.isEmpty()) return - - scope.handledLaunch { - val full = - routeDiscovery.getTracerouteResponse( - getUser = { num -> - val user = nodeRepository.getUser(num) - "${user.long_name} (${user.short_name})" - }, - headerTowards = getStringSuspend(Res.string.traceroute_route_towards_dest), - headerBack = getStringSuspend(Res.string.traceroute_route_back_to_us), - ) - - val requestId = packet.decoded?.request_id ?: 0 - - if (logUuid != null) { - logInsertJob?.join() - val routeNodeNums = (forwardRoute + returnRoute).distinct() - val nodeDbByNum = nodeRepository.nodeDBbyNum.value - val snapshotPositions = - routeNodeNums.mapNotNull { num -> nodeDbByNum[num]?.validPosition?.let { num to it } }.toMap() - tracerouteSnapshotRepository.upsertSnapshotPositions(logUuid, requestId, snapshotPositions) - } - - val start = startTimes.value[requestId] - startTimes.update { it.remove(requestId) } - val responseText = - if (start != null) { - val elapsedMs = nowMillis - start - val seconds = elapsedMs / MILLIS_PER_SECOND - Logger.i { "Traceroute $requestId complete in $seconds s" } - "$full\n\nDuration: ${NumberFormatter.format(seconds, 1)} s" - } else { - full - } - - val destination = forwardRoute.firstOrNull() ?: returnRoute.lastOrNull() ?: 0 - - serviceRepository.setTracerouteResponse( - TracerouteResponse( - message = responseText, - destinationNodeNum = destination, - requestId = requestId, - forwardRoute = forwardRoute, - returnRoute = returnRoute, - logUuid = logUuid, - ), - ) - } - } - - companion object { - private const val MILLIS_PER_SECOND = 1000.0 - } -} diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/HistoryManagerImplTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/HistoryManagerImplTest.kt deleted file mode 100644 index ec62f687c..000000000 --- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/HistoryManagerImplTest.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.data.manager - -import kotlin.test.Test -import kotlin.test.assertEquals - -class HistoryManagerImplTest { - - @Test - fun `resolveHistoryRequestParameters uses config values when positive`() { - val (window, max) = HistoryManagerImpl.resolveHistoryRequestParameters(window = 30, max = 10) - - assertEquals(30, window) - assertEquals(10, max) - } - - @Test - fun `resolveHistoryRequestParameters falls back to defaults when non-positive`() { - val (window, max) = HistoryManagerImpl.resolveHistoryRequestParameters(window = 0, max = -5) - - assertEquals(1440, window) - assertEquals(100, max) - } -} diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImplTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImplTest.kt deleted file mode 100644 index 215ac6c3f..000000000 --- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImplTest.kt +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.data.manager - -import dev.mokkery.MockMode -import dev.mokkery.matcher.any -import dev.mokkery.mock -import dev.mokkery.verify.VerifyMode -import dev.mokkery.verify -import dev.mokkery.verifySuspend -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.runTest -import okio.ByteString.Companion.toByteString -import org.meshtastic.core.model.DataPacket -import org.meshtastic.core.repository.HistoryManager -import org.meshtastic.core.repository.MeshDataHandler -import org.meshtastic.core.repository.PacketRepository -import org.meshtastic.proto.Data -import org.meshtastic.proto.MeshPacket -import org.meshtastic.proto.PortNum -import org.meshtastic.proto.StoreAndForward -import kotlin.test.BeforeTest -import kotlin.test.Test - -@OptIn(ExperimentalCoroutinesApi::class) -class StoreForwardPacketHandlerImplTest { - - private val packetRepository = mock(MockMode.autofill) - private val historyManager = mock(MockMode.autofill) - private val dataHandler = mock(MockMode.autofill) - - private val testDispatcher = StandardTestDispatcher() - private val testScope = TestScope(testDispatcher) - - private lateinit var handler: StoreForwardPacketHandlerImpl - - private val myNodeNum = 12345 - - @BeforeTest - fun setUp() { - handler = - StoreForwardPacketHandlerImpl( - packetRepository = lazy { packetRepository }, - historyManager = historyManager, - dataHandler = lazy { dataHandler }, - scope = testScope, - ) - } - - private fun makeSfPacket(from: Int, sf: StoreAndForward): MeshPacket { - val payload = StoreAndForward.ADAPTER.encode(sf).toByteString() - return MeshPacket(from = from, decoded = Data(portnum = PortNum.STORE_FORWARD_APP, payload = payload)) - } - - private fun makeDataPacket(from: Int): DataPacket = DataPacket( - id = 1, - time = 1700000000000L, - to = DataPacket.BROADCAST, - from = from, - bytes = null, - dataType = PortNum.STORE_FORWARD_APP.value, - ) - - // ---------- Legacy S&F: stats ---------- - - @Test - fun `handleStoreAndForward stats creates text data packet`() = testScope.runTest { - val sf = - StoreAndForward( - stats = StoreAndForward.Statistics(messages_total = 100, messages_saved = 50, messages_max = 200), - ) - val packet = makeSfPacket(999, sf) - val dataPacket = makeDataPacket(999) - - handler.handleStoreAndForward(packet, dataPacket, myNodeNum) - advanceUntilIdle() - - verify { dataHandler.rememberDataPacket(any(), myNodeNum) } - } - - // ---------- Legacy S&F: history ---------- - - @Test - fun `handleStoreAndForward history creates text packet and updates last request`() = testScope.runTest { - val sf = - StoreAndForward( - history = - StoreAndForward.History(history_messages = 42, window = 3600000, last_request = 1700000000), - ) - val packet = makeSfPacket(999, sf) - val dataPacket = makeDataPacket(999) - - handler.handleStoreAndForward(packet, dataPacket, myNodeNum) - advanceUntilIdle() - - verify { dataHandler.rememberDataPacket(any(), myNodeNum) } - verify { historyManager.updateStoreForwardLastRequest("router_history", 1700000000, "Unknown") } - } - - // ---------- Legacy S&F: heartbeat ---------- - - @Test - fun `handleStoreAndForward heartbeat does not crash`() = testScope.runTest { - val sf = StoreAndForward(heartbeat = StoreAndForward.Heartbeat(period = 900, secondary = 1)) - val packet = makeSfPacket(999, sf) - val dataPacket = makeDataPacket(999) - - handler.handleStoreAndForward(packet, dataPacket, myNodeNum) - advanceUntilIdle() - // No crash, just logs - } - - // ---------- Legacy S&F: text ---------- - - @Test - fun `handleStoreAndForward text with broadcast rr sets to broadcast`() = testScope.runTest { - val sf = - StoreAndForward( - text = "Hello from router".encodeToByteArray().toByteString(), - rr = StoreAndForward.RequestResponse.ROUTER_TEXT_BROADCAST, - ) - val packet = makeSfPacket(999, sf) - val dataPacket = makeDataPacket(999) - - handler.handleStoreAndForward(packet, dataPacket, myNodeNum) - advanceUntilIdle() - - verify { dataHandler.rememberDataPacket(any(), myNodeNum) } - } - - @Test - fun `handleStoreAndForward text without broadcast rr preserves destination`() = testScope.runTest { - val sf = - StoreAndForward( - text = "Direct message".encodeToByteArray().toByteString(), - rr = StoreAndForward.RequestResponse.ROUTER_TEXT_DIRECT, - ) - val packet = makeSfPacket(999, sf) - val dataPacket = makeDataPacket(999) - - handler.handleStoreAndForward(packet, dataPacket, myNodeNum) - advanceUntilIdle() - - verify { dataHandler.rememberDataPacket(any(), myNodeNum) } - } - - // ---------- Legacy S&F: null payload ---------- - - @Test - fun `handleStoreAndForward with null payload returns early`() = testScope.runTest { - val packet = MeshPacket(from = 999, decoded = null) - val dataPacket = makeDataPacket(999) - - handler.handleStoreAndForward(packet, dataPacket, myNodeNum) - advanceUntilIdle() - // No crash - } - - // ---------- Legacy S&F: empty message ---------- - - @Test - fun `handleStoreAndForward with no fields set does not crash`() = testScope.runTest { - val sf = StoreAndForward() - val packet = makeSfPacket(999, sf) - val dataPacket = makeDataPacket(999) - - handler.handleStoreAndForward(packet, dataPacket, myNodeNum) - advanceUntilIdle() - // No crash — falls through to else branch - } -} diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.kt deleted file mode 100644 index 6c013b697..000000000 --- a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.kt +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.data.manager - -import dev.mokkery.MockMode -import dev.mokkery.matcher.any -import dev.mokkery.mock -import dev.mokkery.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.runTest -import okio.ByteString.Companion.toByteString -import org.meshtastic.core.model.DataPacket -import org.meshtastic.core.repository.NodeRepository -import org.meshtastic.core.repository.NotificationManager -import org.meshtastic.proto.Data -import org.meshtastic.proto.DeviceMetrics -import org.meshtastic.proto.EnvironmentMetrics -import org.meshtastic.proto.MeshPacket -import org.meshtastic.proto.PortNum -import org.meshtastic.proto.PowerMetrics -import org.meshtastic.proto.Telemetry -import kotlin.test.BeforeTest -import kotlin.test.Test - -@OptIn(ExperimentalCoroutinesApi::class) -class TelemetryPacketHandlerImplTest { - - private val nodeRepository = mock(MockMode.autofill) - private val notificationManager = mock(MockMode.autofill) - - private val testDispatcher = StandardTestDispatcher() - private val testScope = TestScope(testDispatcher) - - private lateinit var handler: TelemetryPacketHandlerImpl - - private val myNodeNum = 12345 - private val remoteNodeNum = 99999 - - @BeforeTest - fun setUp() { - handler = - TelemetryPacketHandlerImpl( - nodeRepository = nodeRepository, - notificationManager = notificationManager, - scope = testScope, - ) - } - - private fun makeTelemetryPacket(from: Int, telemetry: Telemetry): MeshPacket { - val payload = Telemetry.ADAPTER.encode(telemetry).toByteString() - return MeshPacket( - from = from, - decoded = Data(portnum = PortNum.TELEMETRY_APP, payload = payload), - rx_time = 1700000000, - ) - } - - private fun makeDataPacket(from: Int): DataPacket = DataPacket( - id = 1, - time = 1700000000000L, - to = DataPacket.BROADCAST, - from = from, - bytes = null, - dataType = PortNum.TELEMETRY_APP.value, - ) - - // ---------- Device metrics from local node ---------- - - @Test - fun `local device metrics updates node`() = testScope.runTest { - val telemetry = - Telemetry(time = 1700000000, device_metrics = DeviceMetrics(battery_level = 80, voltage = 4.1f)) - val packet = makeTelemetryPacket(myNodeNum, telemetry) - val dataPacket = makeDataPacket(myNodeNum) - - handler.handleTelemetry(packet, dataPacket, myNodeNum) - advanceUntilIdle() - - verify { nodeRepository.updateNode(myNodeNum, any(), any(), any()) } - } - - // ---------- Device metrics from remote node ---------- - - @Test - fun `remote device metrics updates node`() = testScope.runTest { - val telemetry = - Telemetry(time = 1700000000, device_metrics = DeviceMetrics(battery_level = 90, voltage = 4.2f)) - val packet = makeTelemetryPacket(remoteNodeNum, telemetry) - val dataPacket = makeDataPacket(remoteNodeNum) - - handler.handleTelemetry(packet, dataPacket, myNodeNum) - advanceUntilIdle() - - verify { nodeRepository.updateNode(remoteNodeNum, any(), any(), any()) } - } - - // ---------- Environment metrics ---------- - - @Test - fun `environment metrics updates node with environment data`() = testScope.runTest { - val telemetry = - Telemetry( - time = 1700000000, - environment_metrics = EnvironmentMetrics(temperature = 25.5f, relative_humidity = 60.0f), - ) - val packet = makeTelemetryPacket(remoteNodeNum, telemetry) - val dataPacket = makeDataPacket(remoteNodeNum) - - handler.handleTelemetry(packet, dataPacket, myNodeNum) - advanceUntilIdle() - - verify { nodeRepository.updateNode(remoteNodeNum, any(), any(), any()) } - } - - // ---------- Power metrics ---------- - - @Test - fun `power metrics updates node with power data`() = testScope.runTest { - val telemetry = Telemetry(time = 1700000000, power_metrics = PowerMetrics(ch1_voltage = 3.3f)) - val packet = makeTelemetryPacket(remoteNodeNum, telemetry) - val dataPacket = makeDataPacket(remoteNodeNum) - - handler.handleTelemetry(packet, dataPacket, myNodeNum) - advanceUntilIdle() - - verify { nodeRepository.updateNode(remoteNodeNum, any(), any(), any()) } - } - - // ---------- Telemetry time handling ---------- - - @Test - fun `telemetry with time 0 gets time from dataPacket`() = testScope.runTest { - val telemetry = Telemetry(time = 0, device_metrics = DeviceMetrics(battery_level = 50, voltage = 3.8f)) - val packet = makeTelemetryPacket(myNodeNum, telemetry) - val dataPacket = makeDataPacket(myNodeNum) - - handler.handleTelemetry(packet, dataPacket, myNodeNum) - advanceUntilIdle() - - verify { nodeRepository.updateNode(myNodeNum, any(), any(), any()) } - } - - // ---------- Null payload ---------- - - @Test - fun `handleTelemetry with null decoded payload returns early`() = testScope.runTest { - val packet = MeshPacket(from = myNodeNum, decoded = null) - val dataPacket = makeDataPacket(myNodeNum) - - handler.handleTelemetry(packet, dataPacket, myNodeNum) - advanceUntilIdle() - // No crash - } - - @Test - fun `handleTelemetry with empty payload bytes returns early`() = testScope.runTest { - val packet = - MeshPacket( - from = myNodeNum, - decoded = Data(portnum = PortNum.TELEMETRY_APP, payload = okio.ByteString.EMPTY), - ) - val dataPacket = makeDataPacket(myNodeNum) - - handler.handleTelemetry(packet, dataPacket, myNodeNum) - advanceUntilIdle() - // No crash — decodeOrNull returns null for empty payload - } - - // ---------- Battery notification: healthy battery does NOT trigger ---------- - - @Test - fun `healthy battery level does not trigger low battery notification`() = testScope.runTest { - val telemetry = - Telemetry(time = 1700000000, device_metrics = DeviceMetrics(battery_level = 80, voltage = 4.0f)) - val packet = makeTelemetryPacket(myNodeNum, telemetry) - val dataPacket = makeDataPacket(myNodeNum) - - handler.handleTelemetry(packet, dataPacket, myNodeNum) - advanceUntilIdle() - - // No dispatch call — battery is healthy - } -} diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/HistoryManager.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/HistoryManager.kt deleted file mode 100644 index 0087dde97..000000000 --- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/HistoryManager.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.repository - -import org.meshtastic.proto.ModuleConfig - -/** Interface for managing store-and-forward history replay requests. */ -interface HistoryManager { - /** - * Requests a history replay from the radio. - * - * @param trigger A string identifying the trigger for the request (for logging). - * @param myNodeNum The local node number. - * @param storeForwardConfig The store-and-forward module configuration. - * @param transport The transport method being used (for logging). - */ - fun requestHistoryReplay( - trigger: String, - myNodeNum: Int?, - storeForwardConfig: ModuleConfig.StoreForwardConfig?, - transport: String, - ) - - /** - * Updates the last requested history marker. - * - * @param source A string identifying the source of the update (for logging). - * @param lastRequest The timestamp or sequence number of the last received history message. - * @param transport The transport method being used (for logging). - */ - fun updateStoreForwardLastRequest(source: String, lastRequest: Int, transport: String) -} diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshDataHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshDataHandler.kt deleted file mode 100644 index 4879d757e..000000000 --- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshDataHandler.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.repository - -import kotlinx.coroutines.Job -import org.meshtastic.core.model.DataPacket -import org.meshtastic.proto.MeshPacket - -/** Interface for handling incoming mesh data packets and routing them to the appropriate handlers. */ -interface MeshDataHandler { - /** - * Processes a received mesh packet. - * - * @param packet The received mesh packet. - * @param myNodeNum The local node number. - * @param logUuid Optional UUID for logging purposes. - * @param logInsertJob Optional job that tracks the insertion of the packet into the log. - */ - fun handleReceivedData(packet: MeshPacket, myNodeNum: Int, logUuid: String? = null, logInsertJob: Job? = null) - - /** - * Persists a data packet in the history and triggers notifications if necessary. - * - * @param dataPacket The data packet to remember. - * @param myNodeNum The local node number. - * @param updateNotification Whether to trigger a notification for this packet. - */ - fun rememberDataPacket(dataPacket: DataPacket, myNodeNum: Int, updateNotification: Boolean = true) -} diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NeighborInfoHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NeighborInfoHandler.kt deleted file mode 100644 index b924d510a..000000000 --- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NeighborInfoHandler.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.repository - -import org.meshtastic.proto.MeshPacket -import org.meshtastic.proto.NeighborInfo - -/** Interface for handling neighbor info responses from the mesh. */ -interface NeighborInfoHandler { - /** Records the start time for a neighbor info request. */ - fun recordStartTime(requestId: Int) - - /** The latest neighbor info received from the connected radio. */ - var lastNeighborInfo: NeighborInfo? - - /** - * Processes a neighbor info packet. - * - * @param packet The received mesh packet. - */ - fun handleNeighborInfo(packet: MeshPacket) -} diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/StoreForwardPacketHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/StoreForwardPacketHandler.kt deleted file mode 100644 index b7fefddae..000000000 --- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/StoreForwardPacketHandler.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.repository - -import org.meshtastic.core.model.DataPacket -import org.meshtastic.proto.MeshPacket - -/** Interface for handling Store & Forward (legacy) packets. */ -interface StoreForwardPacketHandler { - /** - * Handles a legacy Store & Forward packet. - * - * @param packet The received mesh packet. - * @param dataPacket The decoded data packet. - * @param myNodeNum The local node number. - */ - fun handleStoreAndForward(packet: MeshPacket, dataPacket: DataPacket, myNodeNum: Int) -} diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TelemetryPacketHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TelemetryPacketHandler.kt deleted file mode 100644 index 31ec30db8..000000000 --- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TelemetryPacketHandler.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.repository - -import org.meshtastic.core.model.DataPacket -import org.meshtastic.proto.MeshPacket - -/** Interface for handling telemetry packets from the mesh, including battery notifications. */ -interface TelemetryPacketHandler { - /** - * Processes a telemetry packet. - * - * @param packet The received mesh packet. - * @param dataPacket The decoded data packet. - * @param myNodeNum The local node number. - */ - fun handleTelemetry(packet: MeshPacket, dataPacket: DataPacket, myNodeNum: Int) -} diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TracerouteHandler.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TracerouteHandler.kt deleted file mode 100644 index c0cc42aa2..000000000 --- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TracerouteHandler.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2026 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.meshtastic.core.repository - -import kotlinx.coroutines.Job -import org.meshtastic.proto.MeshPacket - -/** Interface for handling traceroute responses from the mesh. */ -interface TracerouteHandler { - /** Records the start time for a traceroute request. */ - fun recordStartTime(requestId: Int) - - /** - * Processes a traceroute packet. - * - * @param packet The received mesh packet. - * @param logUuid Optional UUID for the associated log entry. - * @param logInsertJob Optional job for the log entry insertion, to ensure ordering. - */ - fun handleTraceroute(packet: MeshPacket, logUuid: String?, logInsertJob: Job?) -}