feat/decoupling (#4685)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-03 07:15:28 -06:00 committed by GitHub
parent 40244f8337
commit 2c49db8041
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
254 changed files with 5132 additions and 2666 deletions

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
/** Interface for triggering updates to application widgets. */
interface AppWidgetUpdater {
/** Triggers an update for all app widgets. */
suspend fun updateAll()
}

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import okio.ByteString
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.Position
import org.meshtastic.proto.AdminMessage
import org.meshtastic.proto.ChannelSet
import org.meshtastic.proto.LocalConfig
import org.meshtastic.proto.NeighborInfo
/** Interface for sending commands and packets to the mesh network. */
@Suppress("TooManyFunctions")
interface CommandSender {
/** Starts the command sender with the given coroutine scope. */
fun start(scope: CoroutineScope)
/** Returns the current packet ID. */
fun getCurrentPacketId(): Long
/** Returns the cached local configuration. */
fun getCachedLocalConfig(): LocalConfig
/** Returns the cached channel set. */
fun getCachedChannelSet(): ChannelSet
/** Generates a new unique packet ID. */
fun generatePacketId(): Int
/** The latest neighbor info received from the connected radio. */
var lastNeighborInfo: NeighborInfo?
/** Start times of traceroute requests for duration calculation. */
val tracerouteStartTimes: MutableMap<Int, Long>
/** Start times of neighbor info requests for duration calculation. */
val neighborInfoStartTimes: MutableMap<Int, Long>
/** Sets the session passkey for admin messages. */
fun setSessionPasskey(key: ByteString)
/** Sends a data packet to the mesh. */
fun sendData(p: DataPacket)
/** Sends an admin message to a specific node. */
fun sendAdmin(
destNum: Int,
requestId: Int = generatePacketId(),
wantResponse: Boolean = false,
initFn: () -> AdminMessage,
)
/** Sends our current position to the mesh. */
fun sendPosition(pos: org.meshtastic.proto.Position, destNum: Int? = null, wantResponse: Boolean = false)
/** Requests the position of a specific node. */
fun requestPosition(destNum: Int, currentPosition: Position)
/** Sets a fixed position for a node. */
fun setFixedPosition(destNum: Int, pos: Position)
/** Requests user info from a specific node. */
fun requestUserInfo(destNum: Int)
/** Requests a traceroute to a specific node. */
fun requestTraceroute(requestId: Int, destNum: Int)
/** Requests telemetry from a specific node. */
fun requestTelemetry(requestId: Int, destNum: Int, typeValue: Int)
/** Requests neighbor info from a specific node. */
fun requestNeighborInfo(requestId: Int, destNum: Int)
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.flow.StateFlow
/** Interface for managing database instances and cache limits. */
interface DatabaseManager {
/** Reactive stream of the current database cache limit. */
val cacheLimit: StateFlow<Int>
/** Returns the current database cache limit from storage. */
fun getCurrentCacheLimit(): Int
/** Sets the database cache limit. */
fun setCacheLimit(limit: Int)
/** Switches the active database to the one associated with the given [address]. */
suspend fun switchActiveDatabase(address: String?)
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import org.meshtastic.core.model.DeviceHardware
interface DeviceHardwareRepository {
/**
* Retrieves device hardware information by its model ID and optional target string.
*
* @param hwModel The hardware model identifier.
* @param target Optional PlatformIO target environment name to disambiguate multiple variants.
* @param forceRefresh If true, the local cache will be invalidated and data will be fetched remotely.
* @return A [Result] containing the [DeviceHardware] on success (or null if not found), or an exception on failure.
*/
suspend fun getDeviceHardwareByModel(
hwModel: Int,
target: String? = null,
forceRefresh: Boolean = false,
): Result<DeviceHardware?>
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import org.meshtastic.proto.FromRadio
/** Interface for dispatching non-packet [FromRadio] variants to their respective handlers. */
interface FromRadioPacketHandler {
/** Processes a [FromRadio] message. */
fun handleFromRadio(proto: FromRadio)
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
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)
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
interface HomoglyphPrefs {
val homoglyphEncodingEnabled: Boolean
}

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.MeshUser
import org.meshtastic.core.model.Position
import org.meshtastic.core.model.service.ServiceAction
/** Interface for handling UI-triggered actions and administrative commands for the mesh. */
@Suppress("TooManyFunctions")
interface MeshActionHandler {
/** Starts the handler with the given coroutine scope. */
fun start(scope: CoroutineScope)
/** Processes a service action from the UI. */
fun onServiceAction(action: ServiceAction)
/** Sets the owner of the local node. */
fun handleSetOwner(u: MeshUser, myNodeNum: Int)
/** Sends a data packet through the mesh. */
fun handleSend(p: DataPacket, myNodeNum: Int)
/** Requests the position of a remote node. */
fun handleRequestPosition(destNum: Int, position: Position, myNodeNum: Int)
/** Removes a node from the database by its node number. */
fun handleRemoveByNodenum(nodeNum: Int, requestId: Int, myNodeNum: Int)
/** Sets the owner of a remote node. */
fun handleSetRemoteOwner(id: Int, destNum: Int, payload: ByteArray)
/** Gets the owner of a remote node. */
fun handleGetRemoteOwner(id: Int, destNum: Int)
/** Sets the configuration of the local node. */
fun handleSetConfig(payload: ByteArray, myNodeNum: Int)
/** Sets the configuration of a remote node. */
fun handleSetRemoteConfig(id: Int, destNum: Int, payload: ByteArray)
/** Gets the configuration of a remote node. */
fun handleGetRemoteConfig(id: Int, destNum: Int, config: Int)
/** Sets the module configuration of a remote node. */
fun handleSetModuleConfig(id: Int, destNum: Int, payload: ByteArray)
/** Gets the module configuration of a remote node. */
fun handleGetModuleConfig(id: Int, destNum: Int, config: Int)
/** Sets the ringtone of a remote node. */
fun handleSetRingtone(destNum: Int, ringtone: String)
/** Gets the ringtone of a remote node. */
fun handleGetRingtone(id: Int, destNum: Int)
/** Sets canned messages on a remote node. */
fun handleSetCannedMessages(destNum: Int, messages: String)
/** Gets canned messages from a remote node. */
fun handleGetCannedMessages(id: Int, destNum: Int)
/** Sets a channel configuration on the local node. */
fun handleSetChannel(payload: ByteArray?, myNodeNum: Int)
/** Sets a channel configuration on a remote node. */
fun handleSetRemoteChannel(id: Int, destNum: Int, payload: ByteArray?)
/** Gets a channel configuration from a remote node. */
fun handleGetRemoteChannel(id: Int, destNum: Int, index: Int)
/** Requests neighbor information from a remote node. */
fun handleRequestNeighborInfo(requestId: Int, destNum: Int)
/** Begins editing settings on a remote node. */
fun handleBeginEditSettings(destNum: Int)
/** Commits settings edits on a remote node. */
fun handleCommitEditSettings(destNum: Int)
/** Reboots a remote node into DFU mode. */
fun handleRebootToDfu(destNum: Int)
/** Requests telemetry from a remote node. */
fun handleRequestTelemetry(requestId: Int, destNum: Int, type: Int)
/** Requests a remote node to shut down. */
fun handleRequestShutdown(requestId: Int, destNum: Int)
/** Requests a remote node to reboot. */
fun handleRequestReboot(requestId: Int, destNum: Int)
/** Requests a remote node to reboot in OTA mode. */
fun handleRequestRebootOta(requestId: Int, destNum: Int, mode: Int, hash: ByteArray?)
/** Requests a factory reset on a remote node. */
fun handleRequestFactoryReset(requestId: Int, destNum: Int)
/** Requests a node database reset on a remote node. */
fun handleRequestNodedbReset(requestId: Int, destNum: Int, preserveFavorites: Boolean)
/** Gets the connection status of a remote node. */
fun handleGetDeviceConnectionStatus(requestId: Int, destNum: Int)
/** Updates the last used device address. */
fun handleUpdateLastAddress(deviceAddr: String?)
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.DeviceMetadata
import org.meshtastic.proto.MyNodeInfo
import org.meshtastic.proto.NodeInfo
/** Interface for managing the configuration flow, including local node info and metadata. */
interface MeshConfigFlowManager {
/** Starts the manager with the given coroutine scope. */
fun start(scope: CoroutineScope)
/** Handles received local node information. */
fun handleMyInfo(myInfo: MyNodeInfo)
/** Handles received local device metadata. */
fun handleLocalMetadata(metadata: DeviceMetadata)
/** Handles received node information. */
fun handleNodeInfo(info: NodeInfo)
/** Returns the number of nodes received in the current stage. */
val newNodeCount: Int
/** Handles the completion of a configuration stage. */
fun handleConfigComplete(configCompleteId: Int)
/** Triggers a request for the full device configuration. */
fun triggerWantConfig()
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import org.meshtastic.proto.Channel
import org.meshtastic.proto.Config
import org.meshtastic.proto.LocalConfig
import org.meshtastic.proto.LocalModuleConfig
import org.meshtastic.proto.ModuleConfig
/** Interface for handling device and module configuration updates. */
interface MeshConfigHandler {
/** Starts the handler with the given coroutine scope. */
fun start(scope: CoroutineScope)
/** Reactive local configuration. */
val localConfig: StateFlow<LocalConfig>
/** Reactive local module configuration. */
val moduleConfig: StateFlow<LocalModuleConfig>
/** Handles a received device configuration. */
fun handleDeviceConfig(config: Config)
/** Handles a received module configuration. */
fun handleModuleConfig(config: ModuleConfig)
/** Handles a received channel configuration. */
fun handleChannel(channel: Channel)
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.Telemetry
/** Interface for managing the connection lifecycle and status with the mesh radio. */
interface MeshConnectionManager {
/** Starts the connection manager with the given coroutine scope. */
fun start(scope: CoroutineScope)
/** Called when the radio configuration has been fully loaded. */
fun onRadioConfigLoaded()
/** Initiates the configuration synchronization stage. */
fun startConfigOnly()
/** Initiates the node information synchronization stage. */
fun startNodeInfoOnly()
/** Called when the node database is ready and fully populated. */
fun onNodeDbReady()
/** Updates the telemetry information for the local node. */
fun updateTelemetry(t: Telemetry)
/** Updates and returns the current status notification. */
fun updateStatusNotification(telemetry: Telemetry? = null): Any
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
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 {
/** Starts the handler with the given coroutine scope. */
fun start(scope: CoroutineScope)
/**
* 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)
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.Position
/** Interface for managing the local node's location updates and reporting. */
interface MeshLocationManager {
/** Starts location updates and reports them via the given function. */
fun start(scope: CoroutineScope, sendPositionFn: (Position) -> Unit)
/** Stops location updates. */
fun stop()
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.MeshPacket
/** Interface for processing incoming radio messages and mesh packets. */
interface MeshMessageProcessor {
/** Starts the processor with the given coroutine scope. */
fun start(scope: CoroutineScope)
/** Handles a raw message received from the radio. */
fun handleFromRadio(bytes: ByteArray, myNodeNum: Int?)
/** Handles a received mesh packet. */
fun handleReceivedMeshPacket(packet: MeshPacket, myNodeNum: Int?)
/** Clears the buffer of early received packets. */
fun clearEarlyPackets()
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
/** Interface for the central router that orchestrates specialized mesh packet handlers. */
interface MeshRouter {
/** Starts the router and its sub-components with the given coroutine scope. */
fun start(scope: CoroutineScope)
/** Access to the data handler. */
val dataHandler: MeshDataHandler
/** Access to the configuration handler. */
val configHandler: MeshConfigHandler
/** Access to the traceroute handler. */
val tracerouteHandler: TracerouteHandler
/** Access to the neighbor info handler. */
val neighborInfoHandler: NeighborInfoHandler
/** Access to the configuration flow manager. */
val configFlowManager: MeshConfigFlowManager
/** Access to the MQTT manager. */
val mqttManager: MqttManager
/** Access to the action handler. */
val actionHandler: MeshActionHandler
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import org.meshtastic.core.model.Node
import org.meshtastic.proto.ClientNotification
import org.meshtastic.proto.Telemetry
const val SERVICE_NOTIFY_ID = 101
@Suppress("TooManyFunctions")
interface MeshServiceNotifications {
fun clearNotifications()
fun initChannels()
fun updateServiceStateNotification(summaryString: String?, telemetry: Telemetry?): Any
suspend fun updateMessageNotification(
contactKey: String,
name: String,
message: String,
isBroadcast: Boolean,
channelName: String?,
isSilent: Boolean = false,
)
suspend fun updateWaypointNotification(
contactKey: String,
name: String,
message: String,
waypointId: Int,
isSilent: Boolean = false,
)
suspend fun updateReactionNotification(
contactKey: String,
name: String,
emoji: String,
isBroadcast: Boolean,
channelName: String?,
isSilent: Boolean = false,
)
fun showAlertNotification(contactKey: String, name: String, alert: String)
fun showNewNodeSeenNotification(node: Node)
fun showOrUpdateLowBatteryNotification(node: Node, isRemote: Boolean)
fun showClientNotification(clientNotification: ClientNotification)
fun cancelMessageNotification(contactKey: String)
fun cancelLowBatteryNotification(node: Node)
fun clearClientNotification(notification: ClientNotification)
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
/** Interface for managing background workers for mesh-related tasks. */
interface MeshWorkerManager {
/** Enqueues a worker to send a specific packet. */
fun enqueueSendMessage(packetId: Int)
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
/** Interface for filtering messages based on user-configured filter words. */
interface MessageFilter {
/**
* Determines if a message should be filtered.
*
* @param message The message text to check.
* @param isFilteringDisabled Whether filtering is disabled for the current contact.
* @return true if the message should be filtered, false otherwise.
*/
fun shouldFilter(message: String, isFilteringDisabled: Boolean = false): Boolean
/** Rebuilds the internal filter patterns. Should be called after filter words are updated. */
fun rebuildPatterns()
}

View file

@ -0,0 +1,25 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
/**
* Interface for enqueuing background work for transmitting messages. This allows the domain layer to trigger durable
* transmission without depending on Android-specific WorkManager.
*/
interface MessageQueue {
suspend fun enqueue(packetId: Int)
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.MqttClientProxyMessage
/** Interface for managing MQTT proxy communication. */
interface MqttManager {
/** Starts the MQTT manager with the given coroutine scope and settings. */
fun start(scope: CoroutineScope, enabled: Boolean, proxyToClientEnabled: Boolean)
/** Stops the MQTT manager. */
fun stop()
/** Handles an MQTT proxy message from the radio. */
fun handleMqttProxyMessage(message: MqttClientProxyMessage)
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.MeshPacket
/** Interface for handling neighbor info responses from the mesh. */
interface NeighborInfoHandler {
/** Starts the neighbor info handler with the given coroutine scope. */
fun start(scope: CoroutineScope)
/**
* Processes a neighbor info packet.
*
* @param packet The received mesh packet.
*/
fun handleNeighborInfo(packet: MeshPacket)
}

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import org.meshtastic.core.model.MyNodeInfo
import org.meshtastic.core.model.Node
import org.meshtastic.core.model.NodeInfo
import org.meshtastic.core.model.util.NodeIdLookup
import org.meshtastic.proto.DeviceMetadata
import org.meshtastic.proto.Paxcount
import org.meshtastic.proto.StatusMessage
import org.meshtastic.proto.Telemetry
import org.meshtastic.proto.User
import org.meshtastic.proto.NodeInfo as ProtoNodeInfo
import org.meshtastic.proto.Position as ProtoPosition
/** Interface for managing the in-memory node database and processing received node information. */
@Suppress("TooManyFunctions")
interface NodeManager : NodeIdLookup {
/** Reactive map of all nodes by their number. */
val nodeDBbyNodeNum: Map<Int, Node>
/** Reactive map of all nodes by their ID string. */
val nodeDBbyID: Map<String, Node>
/** Whether the node database is ready. */
val isNodeDbReady: StateFlow<Boolean>
/** Sets whether the node database is ready. */
fun setNodeDbReady(ready: Boolean)
/** Whether node database writes are allowed. */
val allowNodeDbWrites: StateFlow<Boolean>
/** Sets whether node database writes are allowed. */
fun setAllowNodeDbWrites(allowed: Boolean)
/** Starts the node manager with the given coroutine scope. */
fun start(scope: CoroutineScope)
/** The local node number. */
var myNodeNum: Int?
/** Loads the cached node database from the repository. */
fun loadCachedNodeDB()
/** Clears the in-memory node database. */
fun clear()
/** Returns information about the local node. */
fun getMyNodeInfo(): MyNodeInfo?
/** Returns the local node ID. */
fun getMyId(): String
/** Returns a list of all known nodes. */
fun getNodes(): List<NodeInfo>
/** Processes a received user packet. */
fun handleReceivedUser(fromNum: Int, p: User, channel: Int = 0, manuallyVerified: Boolean = false)
/** Processes a received position packet. */
fun handleReceivedPosition(fromNum: Int, myNodeNum: Int, p: ProtoPosition, defaultTime: Long)
/** Processes a received telemetry packet. */
fun handleReceivedTelemetry(fromNum: Int, telemetry: Telemetry)
/** Processes a received paxcounter packet. */
fun handleReceivedPaxcounter(fromNum: Int, p: Paxcount)
/** Processes a received node status message. */
fun handleReceivedNodeStatus(fromNum: Int, s: StatusMessage)
/** Updates the status string for a node. */
fun updateNodeStatus(nodeNum: Int, status: String?)
/** Updates a node using a transformation function. */
fun updateNode(nodeNum: Int, withBroadcast: Boolean = true, channel: Int = 0, transform: (Node) -> Node)
/** Removes a node from the in-memory database by its number. */
fun removeByNodenum(nodeNum: Int)
/** Installs node information from a ProtoNodeInfo object. */
fun installNodeInfo(info: ProtoNodeInfo, withBroadcast: Boolean = true)
/** Inserts hardware metadata for a node. */
fun insertMetadata(nodeNum: Int, metadata: DeviceMetadata)
}

View file

@ -0,0 +1,177 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import org.meshtastic.core.model.MyNodeInfo
import org.meshtastic.core.model.Node
import org.meshtastic.core.model.NodeSortOption
import org.meshtastic.proto.DeviceMetadata
import org.meshtastic.proto.LocalStats
import org.meshtastic.proto.User
/**
* Repository interface for managing node-related data.
*
* This component provides access to the mesh's node database, local device information, and mesh-wide statistics. It
* supports reactive queries for node lists, counts, and filtered/sorted views.
*
* This interface is shared across platforms via Kotlin Multiplatform (KMP).
*/
@Suppress("TooManyFunctions")
interface NodeRepository {
/** Reactive flow of hardware info about our local radio device. */
val myNodeInfo: StateFlow<MyNodeInfo?>
/**
* Reactive flow of information about the locally connected node as seen by the mesh.
*
* This includes its position, telemetry, and user information as reflected in the mesh's node DB.
*/
val ourNodeInfo: StateFlow<Node?>
/** The unique userId (hex string, e.g., "!1234abcd") of our local node. */
val myId: StateFlow<String?>
/** Reactive flow of the latest local stats telemetry received from the radio. */
val localStats: StateFlow<LocalStats>
/** A reactive map of all known nodes in the mesh, keyed by their 32-bit node number. */
val nodeDBbyNum: StateFlow<Map<Int, Node>>
/** Flow emitting the count of nodes currently considered "online" (heard from recently). */
val onlineNodeCount: Flow<Int>
/** Flow emitting the total number of nodes in the database. */
val totalNodeCount: Flow<Int>
/**
* Updates the cached local stats telemetry.
*
* @param stats The new [LocalStats].
*/
fun updateLocalStats(stats: LocalStats)
/**
* Returns the node number used for log queries.
*
* Maps the local node's number to a constant (e.g., 0) to distinguish it from remote logs.
*/
fun effectiveLogNodeId(nodeNum: Int): Flow<Int>
/**
* Returns the [Node] associated with a given [userId].
*
* @param userId The hex string identifier.
* @return The found [Node] or a fallback object.
*/
fun getNode(userId: String): Node
/**
* Returns the [User] info for a given [nodeNum].
*
* @param nodeNum The 32-bit node number.
* @return The associated [User] proto.
*/
fun getUser(nodeNum: Int): User
/**
* Returns the [User] info for a given [userId].
*
* @param userId The hex string identifier.
* @return The associated [User] proto.
*/
fun getUser(userId: String): User
/**
* Returns a reactive flow of nodes filtered and sorted according to the parameters.
*
* @param sort The [NodeSortOption] to apply.
* @param filter A search string for filtering by name or ID.
* @param includeUnknown Whether to include nodes with unset hardware models.
* @param onlyOnline Whether to include only nodes currently considered online.
* @param onlyDirect Whether to include only nodes heard directly (0 hops away).
*/
fun getNodes(
sort: NodeSortOption = NodeSortOption.LAST_HEARD,
filter: String = "",
includeUnknown: Boolean = true,
onlyOnline: Boolean = false,
onlyDirect: Boolean = false,
): Flow<List<Node>>
/** Returns all nodes that haven't been heard from since the given timestamp. */
suspend fun getNodesOlderThan(lastHeard: Int): List<Node>
/** Returns all nodes with unknown hardware models. */
suspend fun getUnknownNodes(): List<Node>
/**
* Deletes all nodes from the database.
*
* @param preserveFavorites If true, nodes marked as favorite will not be deleted.
*/
suspend fun clearNodeDB(preserveFavorites: Boolean = false)
/** Clears the local node's connection info from the cache. */
suspend fun clearMyNodeInfo()
/**
* Deletes a specific node by its node number.
*
* @param num The node number to delete.
*/
suspend fun deleteNode(num: Int)
/**
* Deletes multiple nodes by their node numbers.
*
* @param nodeNums The list of node numbers to delete.
*/
suspend fun deleteNodes(nodeNums: List<Int>)
/**
* Updates the personal notes for a node.
*
* @param num The node number.
* @param notes The human-readable notes to persist.
*/
suspend fun setNodeNotes(num: Int, notes: String)
/**
* Upserts a [Node] into the persistent database.
*
* @param node The [Node] model to save.
*/
suspend fun upsert(node: Node)
/**
* Installs initial configuration data (local info and remote nodes) into the database.
*
* Used during the initial connection handshake.
*/
suspend fun installConfig(mi: MyNodeInfo, nodes: List<Node>)
/**
* Persists hardware metadata for a node.
*
* @param nodeNum The node number.
* @param metadata The [DeviceMetadata] to save.
*/
suspend fun insertMetadata(nodeNum: Int, metadata: DeviceMetadata)
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import org.meshtastic.proto.MeshPacket
import org.meshtastic.proto.QueueStatus
import org.meshtastic.proto.ToRadio
/** Interface for handling the transmission of packets to the radio and managing the packet queue. */
interface PacketHandler {
/** Starts the packet handler with the given coroutine scope. */
fun start(scope: CoroutineScope)
/** Sends a command/packet directly to the radio. */
fun sendToRadio(p: ToRadio)
/** Adds a mesh packet to the queue for sending. */
fun sendToRadio(packet: MeshPacket)
/** Processes queue status updates from the radio. */
fun handleQueueStatus(queueStatus: QueueStatus)
/** Removes a pending response for a request. */
fun removeResponse(dataRequestId: Int, complete: Boolean)
/** Stops the packet queue. */
fun stopPacketQueue()
}

View file

@ -0,0 +1,213 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import org.meshtastic.core.model.ContactSettings
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.Message
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.model.Node
import org.meshtastic.core.model.Reaction
import org.meshtastic.proto.ChannelSettings
/**
* Repository interface for managing mesh packets and message history.
*
* This component provides methods for persisting received packets, querying message history, tracking unread counts,
* and managing contact-specific settings. It supports both reactive (Flow) and one-shot (suspend) queries.
*/
@Suppress("TooManyFunctions")
interface PacketRepository {
/** Reactive flow of all persisted waypoints (GPS locations). */
fun getWaypoints(): Flow<List<DataPacket>>
/** Reactive flow of all conversation contacts, keyed by their contact identifier. */
fun getContacts(): Flow<Map<String, DataPacket>>
/** Reactive paged flow of conversation contacts. */
fun getContactsPaged(): Flow<PagingData<DataPacket>>
/** Returns the total number of messages in a conversation. */
suspend fun getMessageCount(contact: String): Int
/** Returns the count of unread messages in a conversation. */
suspend fun getUnreadCount(contact: String): Int
/** Reactive flow of the UUID of the first unread message in a conversation. */
fun getFirstUnreadMessageUuid(contact: String): Flow<Long?>
/** Reactive flow indicating whether a conversation has any unread messages. */
fun hasUnreadMessages(contact: String): Flow<Boolean>
/** Reactive flow of the total unread message count across all conversations. */
fun getUnreadCountTotal(): Flow<Int>
/** Clears the unread status for messages in a conversation up to the given timestamp. */
suspend fun clearUnreadCount(contact: String, timestamp: Long)
/** Updates the identifier of the last read message in a conversation. */
suspend fun updateLastReadMessage(contact: String, messageUuid: Long, lastReadTimestamp: Long)
/** Returns all packets currently queued for transmission. */
suspend fun getQueuedPackets(): List<DataPacket>?
/**
* Persists a packet in the database.
*
* @param myNodeNum The local node number at the time of receipt.
* @param contactKey The identifier of the associated conversation.
* @param packet The [DataPacket] to save.
* @param receivedTime The timestamp (ms) the packet was received.
* @param read Whether the packet should be marked as already read.
* @param filtered Whether the packet was filtered by message rules.
*/
suspend fun savePacket(
myNodeNum: Int,
contactKey: String,
packet: DataPacket,
receivedTime: Long,
read: Boolean = true,
filtered: Boolean = false,
)
/**
* Returns a reactive flow of messages for a conversation.
*
* @param contact The conversation identifier.
* @param limit Optional maximum number of messages to return.
* @param includeFiltered Whether to include messages that were marked as filtered.
* @param getNode Callback to fetch node info for message sender attribution.
*/
suspend fun getMessagesFrom(
contact: String,
limit: Int? = null,
includeFiltered: Boolean = true,
getNode: suspend (String?) -> Node,
): Flow<List<Message>>
/** Returns a paged flow of messages for a conversation. */
fun getMessagesFromPaged(contact: String, getNode: suspend (String?) -> Node): Flow<PagingData<Message>>
/** Returns a paged flow of messages for a conversation, with filtering options. */
fun getMessagesFromPaged(
contactKey: String,
includeFiltered: Boolean,
getNode: suspend (String?) -> Node,
): Flow<PagingData<Message>>
/** Updates the transmission status of a packet. */
suspend fun updateMessageStatus(d: DataPacket, m: MessageStatus)
/** Updates the identifier of a persisted packet. */
suspend fun updateMessageId(d: DataPacket, id: Int)
/** Deletes messages by their database UUIDs. */
suspend fun deleteMessages(uuidList: List<Long>)
/** Deletes all messages and settings for the given contacts. */
suspend fun deleteContacts(contactList: List<String>)
/** Deletes a waypoint by its ID. */
suspend fun deleteWaypoint(id: Int)
/** Reactive flow of all contact settings (e.g., mute status). */
fun getContactSettings(): Flow<Map<String, ContactSettings>>
/** Returns the settings for a specific contact. */
suspend fun getContactSettings(contact: String): ContactSettings
/** Mutes the given contacts until the specified timestamp. */
suspend fun setMuteUntil(contacts: List<String>, until: Long)
/** Reactive flow of the number of filtered messages for a contact. */
fun getFilteredCountFlow(contactKey: String): Flow<Int>
/** Returns the total count of filtered messages for a contact. */
suspend fun getFilteredCount(contactKey: String): Int
/** Disables or enables message filtering for a specific contact. */
suspend fun setContactFilteringDisabled(contactKey: String, disabled: Boolean)
/** Clears all packet and message history from the database. */
suspend fun clearPacketDB()
/** Migrates channel-specific message history when encryption keys change. */
suspend fun migrateChannelsByPSK(oldSettings: List<ChannelSettings>, newSettings: List<ChannelSettings>)
/** Marks all messages from a specific sender as filtered or unfiltered. */
suspend fun updateFilteredBySender(senderId: String, filtered: Boolean)
/** Returns a packet by its mesh-layer packet ID. */
suspend fun getPacketByPacketId(packetId: Int): DataPacket?
/** Returns a packet by its internal database ID. */
suspend fun getPacketById(id: Int): DataPacket?
/** Inserts a packet into the database. */
suspend fun insert(
packet: DataPacket,
myNodeNum: Int,
contactKey: String,
receivedTime: Long,
read: Boolean = true,
filtered: Boolean = false,
)
/** Updates an existing packet in the database. */
suspend fun update(packet: DataPacket)
/** Persists a message reaction (emoji). */
suspend fun insertReaction(reaction: Reaction, myNodeNum: Int)
/** Updates an existing reaction. */
suspend fun updateReaction(reaction: Reaction)
/** Returns a reaction associated with a specific packet ID. */
suspend fun getReactionByPacketId(packetId: Int): Reaction?
/** Finds all packets matching a specific packet ID. */
suspend fun findPacketsWithId(packetId: Int): List<DataPacket>
/** Finds all reactions associated with a specific packet ID. */
suspend fun findReactionsWithId(packetId: Int): List<Reaction>
/**
* Updates the Store-and-Forward PlusPlus (SFPP) status for packets.
*
* @param packetId The packet ID.
* @param from The sender node number.
* @param to The recipient node number.
* @param hash The SFPP commit hash.
* @param status The new SFPP-specific message status.
* @param rxTime The receipt time from the mesh.
* @param myNodeNum The local node number.
*/
suspend fun updateSFPPStatus(
packetId: Int,
from: Int,
to: Int,
hash: ByteArray,
status: MessageStatus,
rxTime: Long,
myNodeNum: Int?,
)
/** Updates the SFPP status of packets matching the given commit hash. */
suspend fun updateSFPPStatusByHash(hash: ByteArray, status: MessageStatus, rxTime: Long)
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.flow.Flow
import org.meshtastic.proto.Channel
import org.meshtastic.proto.ChannelSet
import org.meshtastic.proto.ChannelSettings
import org.meshtastic.proto.Config
import org.meshtastic.proto.DeviceProfile
import org.meshtastic.proto.LocalConfig
import org.meshtastic.proto.LocalModuleConfig
import org.meshtastic.proto.ModuleConfig
interface RadioConfigRepository {
/** Flow representing the [ChannelSet] data store. */
val channelSetFlow: Flow<ChannelSet>
/** Clears the [ChannelSet] data in the data store. */
suspend fun clearChannelSet()
/** Replaces the [ChannelSettings] list with a new [settingsList]. */
suspend fun replaceAllSettings(settingsList: List<ChannelSettings>)
/** Updates the [ChannelSettings] list with the provided channel. */
suspend fun updateChannelSettings(channel: Channel)
/** Flow representing the [LocalConfig] data store. */
val localConfigFlow: Flow<LocalConfig>
/** Clears the [LocalConfig] data in the data store. */
suspend fun clearLocalConfig()
/** Updates [LocalConfig] from each [Config] oneOf. */
suspend fun setLocalConfig(config: Config)
/** Flow representing the [LocalModuleConfig] data store. */
val moduleConfigFlow: Flow<LocalModuleConfig>
/** Clears the [LocalModuleConfig] data in the data store. */
suspend fun clearLocalModuleConfig()
/** Updates [LocalModuleConfig] from each [ModuleConfig] oneOf. */
suspend fun setLocalModuleConfig(config: ModuleConfig)
/** Flow representing the combined [DeviceProfile] protobuf. */
val deviceProfileFlow: Flow<DeviceProfile>
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.InterfaceId
import org.meshtastic.core.model.MeshActivity
/** Interface for the low-level radio interface that handles raw byte communication. */
interface RadioInterfaceService {
/** Reactive connection state of the radio. */
val connectionState: StateFlow<ConnectionState>
/** Flow of the current device address. */
val currentDeviceAddressFlow: StateFlow<String?>
/** Whether we are currently using a mock interface. */
fun isMockInterface(): Boolean
/** Flow of raw data received from the radio. */
val receivedData: SharedFlow<ByteArray>
/** Flow of radio activity events. */
val meshActivity: SharedFlow<MeshActivity>
/** Sends a raw byte array to the radio. */
fun sendToRadio(bytes: ByteArray)
/** Initiates the connection to the radio. */
fun connect()
/** Returns the current device address. */
fun getDeviceAddress(): String?
/** Sets the device address to connect to. */
fun setDeviceAddress(deviceAddr: String?): Boolean
/** Constructs a full radio address for the specific interface type. */
fun toInterfaceAddress(interfaceId: InterfaceId, rest: String): String
/** Called by an interface when it has successfully connected. */
fun onConnect()
/** Called by an interface when it has disconnected. */
fun onDisconnect(isPermanent: Boolean)
/** Called by an interface when it has disconnected with an error. */
fun onDisconnect(error: Any)
/** Called by an interface when it has received raw data from the radio. */
fun handleFromRadio(bytes: ByteArray)
/** The scope in which interface-related coroutines should run. */
val serviceScope: CoroutineScope
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.model.Node
/** Interface for broadcasting service-level events to the application. */
interface ServiceBroadcasts {
/** Subscribes a receiver to mesh broadcasts. */
fun subscribeReceiver(receiverName: String, packageName: String)
/** Broadcasts received data to the application. */
fun broadcastReceivedData(dataPacket: DataPacket)
/** Broadcasts that the radio connection state has changed. */
fun broadcastConnection()
/** Broadcasts that node information has changed. */
fun broadcastNodeChange(node: Node)
/** Broadcasts that the status of a message has changed. */
fun broadcastMessageStatus(packetId: Int, status: MessageStatus)
}

View file

@ -0,0 +1,147 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import co.touchlab.kermit.Severity
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.service.ServiceAction
import org.meshtastic.core.model.service.TracerouteResponse
import org.meshtastic.proto.ClientNotification
import org.meshtastic.proto.MeshPacket
/**
* Interface for managing background service state, connection status, and mesh events.
*
* This repository acts as the primary data bridge between the long-running mesh service and the UI/Feature layers. It
* maintains reactive flows for connection status, error messages, and incoming mesh traffic.
*/
@Suppress("TooManyFunctions")
interface ServiceRepository {
/** Reactive flow of the current connection state. */
val connectionState: StateFlow<ConnectionState>
/**
* Updates the current connection state.
*
* @param connectionState The new [ConnectionState].
*/
fun setConnectionState(connectionState: ConnectionState)
/**
* Reactive flow of high-level client notifications.
*
* These represent events from the mesh client that may require UI feedback.
*/
val clientNotification: StateFlow<ClientNotification?>
/**
* Sets the current client notification.
*
* @param notification The [ClientNotification] to display or act upon.
*/
fun setClientNotification(notification: ClientNotification?)
/** Clears the current client notification. */
fun clearClientNotification()
/**
* Reactive flow of human-readable error messages.
*
* These are typically shown as snackbars or dialogs in the UI.
*/
val errorMessage: StateFlow<String?>
/**
* Sets an error message to be displayed.
*
* @param text The error message text.
* @param severity The [Severity] level of the error.
*/
fun setErrorMessage(text: String, severity: Severity = Severity.Error)
/** Clears the current error message. */
fun clearErrorMessage()
/**
* Reactive flow of connection progress messages.
*
* Used during the handshake and config loading phase to provide status updates to the user.
*/
val connectionProgress: StateFlow<String?>
/**
* Sets the connection progress message.
*
* @param text The progress description (e.g., "Downloading Node DB...").
*/
fun setConnectionProgress(text: String)
/**
* Flow of all raw [MeshPacket] objects received from the mesh.
*
* Subscribing to this flow allows components to react to any incoming traffic.
*/
val meshPacketFlow: SharedFlow<MeshPacket>
/**
* Emits a mesh packet into the flow.
*
* Called by the packet processor when new data arrives from the radio.
*
* @param packet The received [MeshPacket].
*/
suspend fun emitMeshPacket(packet: MeshPacket)
/** Reactive flow of the most recent traceroute result. */
val tracerouteResponse: StateFlow<TracerouteResponse?>
/**
* Sets the traceroute response.
*
* @param value The [TracerouteResponse] result.
*/
fun setTracerouteResponse(value: TracerouteResponse?)
/** Clears the current traceroute response. */
fun clearTracerouteResponse()
/** Reactive flow of the most recent neighbor info response (formatted string). */
val neighborInfoResponse: StateFlow<String?>
/**
* Sets the neighbor info response.
*
* @param value The human-readable neighbor info string.
*/
fun setNeighborInfoResponse(value: String?)
/** Clears the current neighbor info response. */
fun clearNeighborInfoResponse()
/** Flow of service actions requested by the UI (e.g., "Favorite Node", "Mute Node"). */
val serviceAction: Flow<ServiceAction>
/**
* Dispatches a service action to be handled by the background service.
*
* @param action The [ServiceAction] to perform.
*/
suspend fun onServiceAction(action: ServiceAction)
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import org.meshtastic.proto.MeshPacket
/** Interface for handling traceroute responses from the mesh. */
interface TracerouteHandler {
/** Starts the traceroute handler with the given coroutine scope. */
fun start(scope: CoroutineScope)
/**
* 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?)
}

View file

@ -0,0 +1,138 @@
/*
* Copyright (c) 2025-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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.repository.usecase
import co.touchlab.kermit.Logger
import org.meshtastic.core.common.util.HomoglyphCharacterStringTransformer
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.model.Capabilities
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.model.Node
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.repository.HomoglyphPrefs
import org.meshtastic.core.repository.MessageQueue
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.PacketRepository
import org.meshtastic.proto.Config
import kotlin.random.Random
/**
* Use case for sending a message over the mesh network.
*
* This component orchestrates the process of:
* 1. Resolving the destination and sender information.
* 2. Handling implicit actions for direct messages (e.g., sharing contacts, favoriting).
* 3. Applying message transformations (e.g., homoglyph encoding).
* 4. Persisting the outgoing message in the local history.
* 5. Enqueuing the message for durable delivery via the platform's message queue.
*
* This implementation is platform-agnostic and relies on injected repositories and controllers.
*/
@Suppress("TooGenericExceptionCaught")
class SendMessageUseCase(
private val nodeRepository: NodeRepository,
private val packetRepository: PacketRepository,
private val radioController: RadioController,
private val homoglyphEncodingPrefs: HomoglyphPrefs,
private val messageQueue: MessageQueue,
) {
/**
* Executes the send message workflow.
*
* @param text The plain text message to send.
* @param contactKey The identifier of the target contact or channel (e.g., "0!ffffffff" for broadcast).
* @param replyId Optional ID of a message being replied to.
*/
@Suppress("NestedBlockDepth", "LongMethod", "CyclomaticComplexMethod")
suspend operator fun invoke(
text: String,
contactKey: String = "0${DataPacket.ID_BROADCAST}",
replyId: Int? = null,
) {
val channel = contactKey[0].digitToIntOrNull()
val dest = if (channel != null) contactKey.substring(1) else contactKey
val ourNode = nodeRepository.ourNodeInfo.value
val fromId = ourNode?.user?.id ?: DataPacket.ID_LOCAL
// logic for direct messages
if (channel == null) {
val destNode = nodeRepository.getNode(dest)
val fwVersion = ourNode?.metadata?.firmware_version
val isClientBase = ourNode?.user?.role == Config.DeviceConfig.Role.CLIENT_BASE
val capabilities = Capabilities(fwVersion)
if (capabilities.canSendVerifiedContacts) {
sendSharedContact(destNode)
} else {
if (!destNode.isFavorite && !isClientBase) {
favoriteNode(destNode)
}
}
}
// Apply homoglyph encoding
val finalMessageText =
if (homoglyphEncodingPrefs.homoglyphEncodingEnabled) {
HomoglyphCharacterStringTransformer.optimizeUtf8StringWithHomoglyphs(text)
} else {
text
}
val packetId = Random.nextInt(1, Int.MAX_VALUE)
val packet =
DataPacket(dest, channel ?: 0, finalMessageText, replyId).apply {
from = fromId
id = packetId
status = MessageStatus.QUEUED
}
try {
// Write to the DB to immediately reflect the queued state on the UI
packetRepository.savePacket(
myNodeNum = ourNode?.num ?: 0,
contactKey = contactKey,
packet = packet,
receivedTime = nowMillis,
)
// Enqueue for durable transmission via the platform-specific queue
messageQueue.enqueue(packetId)
} catch (ex: Exception) {
Logger.e(ex) { "Failed to enqueue message packet" }
}
}
private suspend fun favoriteNode(node: Node) {
try {
radioController.favoriteNode(node.num)
} catch (ex: Exception) {
Logger.e(ex) { "Favorite node error" }
}
}
private suspend fun sendSharedContact(node: Node) {
try {
radioController.sendSharedContact(node.num)
} catch (ex: Exception) {
Logger.e(ex) { "Send shared contact error" }
}
}
}