feat: Add disconnect broadcast and improve app port handling

This commit introduces several enhancements to the service broadcasts and data handling:

-   **Disconnect Broadcast**: Adds and triggers a new `ACTION_MESH_DISCONNECTED` broadcast when the mesh connection state changes to `Disconnected`. This provides a more specific intent for apps to listen for disconnection events.

-   **Expanded App Port Handling**:
    -   Adds explicit broadcast actions for various app port numbers (e.g., `ATAK_PLUGIN`, `PRIVATE_APP`, `DETECTION_SENSOR_APP`).
    -   Ensures that packets for `ATAK`, `PRIVATE_APP`, and `DETECTION_SENSOR_APP` are now correctly broadcast to external applications.
    -   Implements a default behavior to broadcast any unrecognized port numbers, allowing for future extensibility and support for third-party apps.

-   **Backward Compatibility**: When broadcasting received data, a secondary broadcast with the numeric port number is also sent to maintain compatibility with older applications that may rely on it.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-02-07 15:10:00 -06:00
parent a493cf1420
commit 8c46a0c946
3 changed files with 40 additions and 5 deletions

View file

@ -20,9 +20,19 @@ const val PREFIX = "com.geeksville.mesh"
const val ACTION_NODE_CHANGE = "$PREFIX.NODE_CHANGE"
const val ACTION_MESH_CONNECTED = "$PREFIX.MESH_CONNECTED"
const val ACTION_MESH_DISCONNECTED = "$PREFIX.MESH_DISCONNECTED"
const val ACTION_CONNECTION_CHANGED = "$PREFIX.CONNECTION_CHANGED"
const val ACTION_MESSAGE_STATUS = "$PREFIX.MESSAGE_STATUS"
const val ACTION_RECEIVED_TEXT_MESSAGE_APP = "$PREFIX.RECEIVED.TEXT_MESSAGE_APP"
const val ACTION_RECEIVED_POSITION_APP = "$PREFIX.RECEIVED.POSITION_APP"
const val ACTION_RECEIVED_NODEINFO_APP = "$PREFIX.RECEIVED.NODEINFO_APP"
const val ACTION_RECEIVED_TELEMETRY_APP = "$PREFIX.RECEIVED.TELEMETRY_APP"
const val ACTION_RECEIVED_ATAK_PLUGIN = "$PREFIX.RECEIVED.ATAK_PLUGIN"
const val ACTION_RECEIVED_ATAK_FORWARDER = "$PREFIX.RECEIVED.ATAK_FORWARDER"
const val ACTION_RECEIVED_DETECTION_SENSOR_APP = "$PREFIX.RECEIVED.DETECTION_SENSOR_APP"
const val ACTION_RECEIVED_PRIVATE_APP = "$PREFIX.RECEIVED.PRIVATE_APP"
fun actionReceived(portNum: String) = "$PREFIX.RECEIVED.$portNum"
//

View file

@ -181,12 +181,25 @@ constructor(
shouldBroadcast = true
}
PortNum.ATAK_PLUGIN,
PortNum.ATAK_FORWARDER,
PortNum.PRIVATE_APP,
-> {
shouldBroadcast = true
}
PortNum.RANGE_TEST_APP,
PortNum.DETECTION_SENSOR_APP,
-> {
handleRangeTest(dataPacket, myNodeNum)
shouldBroadcast = true
}
else -> {
// By default, if we don't know what it is, we should probably broadcast it
// so that external apps can handle it.
shouldBroadcast = true
}
else -> {}
}
return shouldBroadcast
}
@ -420,7 +433,7 @@ constructor(
}
if (shouldDisplay) {
val now = System.currentTimeMillis() / MILLISECONDS_IN_SECOND
if (!batteryPercentCooldowns.containsKey(fromNum)) batteryPercentCooldowns[fromNum] = 0
if (!batteryPercentCooldowns.containsKey(fromNum)) batteryPercentCooldowns[fromNum] = 0L
if ((now - batteryPercentCooldowns[fromNum]!!) >= BATTERY_PERCENT_COOLDOWN_SECONDS || forceDisplay) {
batteryPercentCooldowns[fromNum] = now
return true

View file

@ -25,6 +25,7 @@ import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.model.NodeInfo
import org.meshtastic.core.model.util.toPIIString
import org.meshtastic.core.service.ConnectionState
import org.meshtastic.core.service.ServiceRepository
import java.util.Locale
import javax.inject.Inject
@ -47,7 +48,14 @@ constructor(
/** Broadcast some received data Payload will be a DataPacket */
fun broadcastReceivedData(payload: DataPacket) {
explicitBroadcast(Intent(MeshService.actionReceived(payload.dataType)).putExtra(EXTRA_PAYLOAD, payload))
val action = MeshService.actionReceived(payload.dataType)
explicitBroadcast(Intent(action).putExtra(EXTRA_PAYLOAD, payload))
// Also broadcast with the numeric port number for backwards compatibility with some apps
val numericAction = actionReceived(payload.dataType.toString())
if (numericAction != action) {
explicitBroadcast(Intent(numericAction).putExtra(EXTRA_PAYLOAD, payload))
}
}
fun broadcastNodeChange(info: NodeInfo) {
@ -84,12 +92,16 @@ constructor(
serviceRepository.setConnectionState(connectionState)
explicitBroadcast(intent)
if (connectionState == ConnectionState.Disconnected) {
explicitBroadcast(Intent(ACTION_MESH_DISCONNECTED))
}
// Restore legacy action for other consumers (e.g. mesh_service_example)
val legacyIntent =
Intent(ACTION_CONNECTION_CHANGED).apply {
putExtra(EXTRA_CONNECTED, stateStr)
// Legacy boolean extra often expected by older implementations
putExtra("connected", connectionState == org.meshtastic.core.service.ConnectionState.Connected)
putExtra("connected", connectionState == ConnectionState.Connected)
}
explicitBroadcast(legacyIntent)
}
@ -101,7 +113,7 @@ constructor(
* NODE_CHANGE for new IDs appearing or disappearing
* ACTION_MESH_CONNECTED for losing/gaining connection to the packet radio
* Note: this is not the same as RadioInterfaceService.RADIO_CONNECTED_ACTION,
* because it implies we have assembled a valid node db.
* because it implies we have assembled a warehouse valid node db.
*/
private fun explicitBroadcast(intent: Intent) {
context.sendBroadcast(