mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: implement XModem file transfers and enhance BLE connection robustness (#4959)
Some checks are pending
Dependency Submission / dependency-submission (push) Waiting to run
Main CI (Verify & Build) / validate-and-build (push) Waiting to run
Main Push Changelog / Generate main push changelog (push) Waiting to run
Some checks are pending
Dependency Submission / dependency-submission (push) Waiting to run
Main CI (Verify & Build) / validate-and-build (push) Waiting to run
Main Push Changelog / Generate main push changelog (push) Waiting to run
This commit is contained in:
parent
ae4465d7c8
commit
c75c9b34d6
43 changed files with 1100 additions and 120 deletions
|
|
@ -18,6 +18,7 @@ package org.meshtastic.core.repository
|
|||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.meshtastic.proto.DeviceMetadata
|
||||
import org.meshtastic.proto.FileInfo
|
||||
import org.meshtastic.proto.MyNodeInfo
|
||||
import org.meshtastic.proto.NodeInfo
|
||||
|
||||
|
|
@ -35,6 +36,14 @@ interface MeshConfigFlowManager {
|
|||
/** Handles received node information. */
|
||||
fun handleNodeInfo(info: NodeInfo)
|
||||
|
||||
/**
|
||||
* Handles a [FileInfo] packet received during STATE_SEND_FILEMANIFEST.
|
||||
*
|
||||
* Each packet describes one file available on the device. Accumulated into [RadioConfigRepository.fileManifestFlow]
|
||||
* and cleared at the start of each new handshake.
|
||||
*/
|
||||
fun handleFileInfo(info: FileInfo)
|
||||
|
||||
/** Returns the number of nodes received in the current stage. */
|
||||
val newNodeCount: Int
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.meshtastic.proto.Channel
|
||||
import org.meshtastic.proto.Config
|
||||
import org.meshtastic.proto.DeviceUIConfig
|
||||
import org.meshtastic.proto.LocalConfig
|
||||
import org.meshtastic.proto.LocalModuleConfig
|
||||
import org.meshtastic.proto.ModuleConfig
|
||||
|
|
@ -43,4 +44,10 @@ interface MeshConfigHandler {
|
|||
|
||||
/** Handles a received channel configuration. */
|
||||
fun handleChannel(channel: Channel)
|
||||
|
||||
/**
|
||||
* Handles the [DeviceUIConfig] received during the config handshake (STATE_SEND_UIDATA). This arrives as the 2nd
|
||||
* packet in every handshake, immediately after my_info.
|
||||
*/
|
||||
fun handleDeviceUIConfig(config: DeviceUIConfig)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,4 +43,7 @@ interface MeshRouter {
|
|||
|
||||
/** Access to the action handler. */
|
||||
val actionHandler: MeshActionHandler
|
||||
|
||||
/** Access to the XModem file-transfer manager. */
|
||||
val xmodemManager: XModemManager
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,13 @@ import org.meshtastic.proto.ChannelSet
|
|||
import org.meshtastic.proto.ChannelSettings
|
||||
import org.meshtastic.proto.Config
|
||||
import org.meshtastic.proto.DeviceProfile
|
||||
import org.meshtastic.proto.DeviceUIConfig
|
||||
import org.meshtastic.proto.FileInfo
|
||||
import org.meshtastic.proto.LocalConfig
|
||||
import org.meshtastic.proto.LocalModuleConfig
|
||||
import org.meshtastic.proto.ModuleConfig
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface RadioConfigRepository {
|
||||
/** Flow representing the [ChannelSet] data store. */
|
||||
val channelSetFlow: Flow<ChannelSet>
|
||||
|
|
@ -59,4 +62,30 @@ interface RadioConfigRepository {
|
|||
|
||||
/** Flow representing the combined [DeviceProfile] protobuf. */
|
||||
val deviceProfileFlow: Flow<DeviceProfile>
|
||||
|
||||
/**
|
||||
* Flow of the device's UI configuration, populated from [DeviceUIConfig] during the config handshake
|
||||
* (STATE_SEND_UIDATA — 2nd packet in every handshake). Null until the first handshake completes or after
|
||||
* [clearDeviceUIConfig] is called.
|
||||
*/
|
||||
val deviceUIConfigFlow: Flow<DeviceUIConfig?>
|
||||
|
||||
/** Stores the [DeviceUIConfig] received from the device. */
|
||||
suspend fun setDeviceUIConfig(config: DeviceUIConfig)
|
||||
|
||||
/** Clears the stored [DeviceUIConfig]; called at the start of each new handshake. */
|
||||
suspend fun clearDeviceUIConfig()
|
||||
|
||||
/**
|
||||
* Flow of [FileInfo] packets accumulated during STATE_SEND_FILEMANIFEST.
|
||||
*
|
||||
* Cleared at the start of each new handshake via [clearFileManifest].
|
||||
*/
|
||||
val fileManifestFlow: Flow<List<FileInfo>>
|
||||
|
||||
/** Appends a single [FileInfo] entry to [fileManifestFlow]. */
|
||||
suspend fun addFileInfo(info: FileInfo)
|
||||
|
||||
/** Clears the accumulated file manifest; called at the start of each new handshake. */
|
||||
suspend fun clearFileManifest()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
/** A file received via an XModem transfer from the connected device. */
|
||||
data class XModemFile(
|
||||
/** Filename as set via [XModemManager.setTransferName] before the transfer started. */
|
||||
val name: String,
|
||||
/** Raw bytes of the received file (trailing CTRLZ padding stripped). */
|
||||
val data: ByteArray,
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is XModemFile) return false
|
||||
return name == other.name && data.contentEquals(other.data)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = 31 * name.hashCode() + data.contentHashCode()
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.XModem
|
||||
|
||||
/**
|
||||
* Handles the XModem-CRC receive protocol for file transfers from the connected device.
|
||||
*
|
||||
* The device (sender) initiates transfers in response to admin file-read requests. The Android client (receiver)
|
||||
* acknowledges each 128-byte block and signals end-of-transfer acceptance.
|
||||
*
|
||||
* Usage:
|
||||
* 1. Optionally call [setTransferName] with the filename being requested so the emitted [XModemFile] is labelled
|
||||
* correctly.
|
||||
* 2. Route every [FromRadio.xmodemPacket] here via [handleIncomingXModem].
|
||||
* 3. Collect [fileTransferFlow] to receive completed files.
|
||||
*/
|
||||
interface XModemManager {
|
||||
/**
|
||||
* Hot flow that emits once per completed transfer. Backpressure is handled by a small buffer; older transfers are
|
||||
* dropped if the consumer is slow.
|
||||
*/
|
||||
val fileTransferFlow: Flow<XModemFile>
|
||||
|
||||
/**
|
||||
* Sets the name to attach to the next completed transfer.
|
||||
*
|
||||
* Call this immediately before (or after) sending the admin file-read request to the device so the emitted
|
||||
* [XModemFile] is labelled with the correct path.
|
||||
*/
|
||||
fun setTransferName(name: String)
|
||||
|
||||
/** Routes an incoming XModem packet from the device to the receive state machine. */
|
||||
fun handleIncomingXModem(packet: XModem)
|
||||
|
||||
/** Cancels any in-progress transfer and sends a CAN control byte to the device. */
|
||||
fun cancel()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue