refactor(transport): complete transport architecture overhaul — extract callback, wire BleReconnectPolicy, fix safety issues (#5080)

This commit is contained in:
James Rich 2026-04-11 23:22:18 -05:00 committed by GitHub
parent 962c619c4c
commit e85300531e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 1184 additions and 1018 deletions

View file

@ -16,6 +16,7 @@
*/
package org.meshtastic.core.repository
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.Node
import org.meshtastic.proto.ClientNotification
import org.meshtastic.proto.Telemetry
@ -28,7 +29,7 @@ interface MeshServiceNotifications {
fun initChannels()
fun updateServiceStateNotification(state: org.meshtastic.core.model.ConnectionState, telemetry: Telemetry?)
fun updateServiceStateNotification(state: ConnectionState, telemetry: Telemetry?)
suspend fun updateMessageNotification(
contactKey: String,

View file

@ -39,7 +39,7 @@ import org.meshtastic.core.model.MeshActivity
*
* @see ServiceRepository.connectionState
*/
interface RadioInterfaceService {
interface RadioInterfaceService : RadioTransportCallback {
/** The device types supported by this platform's radio interface. */
val supportedDeviceTypes: List<DeviceType>
@ -65,8 +65,8 @@ interface RadioInterfaceService {
/** Flow of the current device address. */
val currentDeviceAddressFlow: StateFlow<String?>
/** Whether we are currently using a mock interface. */
fun isMockInterface(): Boolean
/** Whether we are currently using a mock transport. */
fun isMockTransport(): Boolean
/** Flow of raw data received from the radio. */
val receivedData: SharedFlow<ByteArray>
@ -89,15 +89,6 @@ interface RadioInterfaceService {
/** 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, errorMessage: String? = null)
/** Called by an interface when it has received raw data from the radio. */
fun handleFromRadio(bytes: ByteArray)
/** Flow of user-facing connection error messages (e.g. permission failures). */
val connectionError: SharedFlow<String>

View file

@ -26,6 +26,14 @@ interface RadioTransport : Closeable {
/** Sends a raw byte array to the radio hardware. */
fun handleSendToRadio(p: ByteArray)
/**
* Initializes the transport after construction. Called by the factory once the transport has been fully created.
*
* This separates construction from side effects (connecting, launching coroutines), making transports easier to
* test and reason about.
*/
fun start() {}
/**
* If we think we are connected, but we don't hear anything from the device, we might be in a zombie state. This
* function can be implemented by transports to see if we are really connected.

View file

@ -0,0 +1,41 @@
/*
* 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
/**
* Narrow callback interface for transport service communication.
*
* Transport implementations ([RadioTransport]) need only these three methods to report lifecycle events and deliver
* data. This replaces the previous pattern of passing the full [RadioInterfaceService] to transport constructors,
* decoupling transports from the service layer.
*/
interface RadioTransportCallback {
/** Called when the transport has successfully established a connection. */
fun onConnect()
/**
* Called when the transport has disconnected.
*
* @param isPermanent true if the device is definitely gone (e.g. USB unplugged, max retries exhausted), false if it
* may come back (e.g. BLE range, TCP transient).
* @param errorMessage optional user-facing error message describing the disconnect reason.
*/
fun onDisconnect(isPermanent: Boolean, errorMessage: String? = null)
/** Called when the transport has received raw data from the radio. */
fun handleFromRadio(bytes: ByteArray)
}

View file

@ -28,8 +28,8 @@ interface RadioTransportFactory {
/** The device types supported by this factory. */
val supportedDeviceTypes: List<DeviceType>
/** Whether we are currently forced into using a mock interface (e.g., Firebase Test Lab). */
fun isMockInterface(): Boolean
/** Whether we are currently forced into using a mock transport (e.g., Firebase Test Lab). */
fun isMockTransport(): Boolean
/** Creates a transport for the given [address], or a NOP implementation if invalid/unsupported. */
fun createTransport(address: String, service: RadioInterfaceService): RadioTransport