feat: Add disconnect broadcast and improve app port handling#4502
Conversation
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>
There was a problem hiding this comment.
Pull request overview
This PR extends the Android app’s broadcast surface for mesh connection state and received data, primarily to improve interoperability with external applications consuming Meshtastic service broadcasts.
Changes:
- Add a new
ACTION_MESH_DISCONNECTEDbroadcast when the connection state isDisconnected. - Emit an additional “numeric port”
RECEIVED.<portNum>broadcast for backwards compatibility when receiving data. - Expand
MeshDataHandlerport handling to broadcast additional ports (ATAK/PRIVATE_APP/etc.) and broadcast unknown ports by default.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| app/src/main/java/com/geeksville/mesh/service/MeshServiceBroadcasts.kt | Adds disconnect broadcast and dual-action received-data broadcasts (named + numeric). |
| app/src/main/java/com/geeksville/mesh/service/MeshDataHandler.kt | Updates port handling to broadcast more app ports and default-broadcast unknown ports; fixes a Long literal init. |
| app/src/main/java/com/geeksville/mesh/service/Constants.kt | Introduces new action constants for disconnect and several received-port actions. |
| // By default, if we don't know what it is, we should probably broadcast it | ||
| // so that external apps can handle it. | ||
| shouldBroadcast = true |
There was a problem hiding this comment.
handleDataPacket() initializes shouldBroadcast to !fromUs, but for any port handled by handleSpecializedDataPacket() that value gets overwritten. With the new else -> shouldBroadcast = true, unknown ports will now be broadcast even when fromUs == true, which is inconsistent with the rest of the handler and can cause apps to receive their own packets unexpectedly. Consider passing fromUs into handleSpecializedDataPacket() and using !fromUs as the default for unknown ports (only overriding to true for the specific ports that need it).
| // By default, if we don't know what it is, we should probably broadcast it | |
| // so that external apps can handle it. | |
| shouldBroadcast = true | |
| // For unknown ports, do not modify shouldBroadcast here. | |
| // Higher-level logic will decide whether to broadcast, which avoids | |
| // accidentally causing the device to receive its own packets. |
| 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" | ||
|
|
There was a problem hiding this comment.
The newly added ACTION_RECEIVED_* constants are not referenced anywhere in the codebase right now. If they’re intended to be the canonical action names, consider using them where intents are created (or documenting them externally); otherwise, removing them will avoid unused API surface and reduce the risk of them drifting out of sync with actionReceived().
| 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 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)) | ||
| } |
There was a problem hiding this comment.
broadcastReceivedData() now sends an additional backward-compat broadcast with a numeric-port action, but there’s no unit test coverage for this new behavior. Since there’s already a Robolectric test suite for MeshServiceBroadcasts, please add a test that verifies both actions are emitted for a known PortNum (and only one action for an unknown port).
| if (connectionState == ConnectionState.Disconnected) { | ||
| explicitBroadcast(Intent(ACTION_MESH_DISCONNECTED)) | ||
| } |
There was a problem hiding this comment.
broadcastConnection() now emits a new ACTION_MESH_DISCONNECTED intent when the state is Disconnected, but the existing MeshServiceBroadcastsTest only asserts the connected/legacy intents. Please add a unit test that sets the state to Disconnected and verifies the disconnect broadcast is sent (and, ideally, that it is not sent for other states).
| 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 | ||
| } |
There was a problem hiding this comment.
handleSpecializedDataPacket() now broadcasts additional port types (ATAK/PRIVATE_APP/DETECTION_SENSOR_APP) and also broadcasts unknown ports by default. There are existing unit tests for MeshDataHandler, but none cover these new broadcast decisions. Please add tests that assert whether serviceBroadcasts.broadcastReceivedData() is (or isn’t) called for these ports, including the fromUs case if the intended behavior is to suppress self-echoes.
| * 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. |
There was a problem hiding this comment.
The KDoc sentence now reads "assembled a warehouse valid node db", which looks like an accidental word insertion and changes the meaning. Please revert to the original wording (e.g., "assembled a valid node db").
| * because it implies we have assembled a warehouse valid node db. | |
| * because it implies we have assembled a valid node db. |
This commit modifies the `onConnectionChanged` function to ensure that all connection state changes are reported, including transitions to the same state. Previously, redundant notifications were suppressed unless the state was `Connected`. This change allows for consistent handling of all connection events. Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4502 +/- ##
==========================================
- Coverage 11.08% 11.05% -0.04%
==========================================
Files 429 429
Lines 14449 14461 +12
Branches 2391 2394 +3
==========================================
- Hits 1602 1598 -4
- Misses 12545 12558 +13
- Partials 302 305 +3 ☔ View full report in Codecov by Sentry. |
Keeps the connection state as 'Connecting' if it was not 'Disconnected' when `handleConnected` is called. This prevents the UI from briefly showing a disconnected state during a reconnection attempt, such as when the device is rebooting. Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit centralizes all Android Intent constants into a new `MeshtasticIntent` object within the `core/api` module. This refactoring makes the constants accessible to external applications and removes the duplicated definitions from the main application. The app and the service example have been updated to use these new centralized constants. Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
The `drainPacketQueueAndDispatch` test is being ignored because it is flaky and causes intermittent CI failures. This appears to be due to timing issues within the Nordic BLE mock library. Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit updates the README files for the `core:api`, `core:model`, and `core:proto` modules to improve documentation and reflect current usage. Key changes include: - Bumping the library version to `v2.7.13` in usage examples. - Adding a detailed README for the `core:proto` module. - Updating the `core:api` README to use `MeshtasticIntent` constants instead of hardcoded strings. Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit introduces several enhancements to the service broadcasts and data handling:
Disconnect Broadcast: Adds and triggers a new
ACTION_MESH_DISCONNECTEDbroadcast when the mesh connection state changes toDisconnected. This provides a more specific intent for apps to listen for disconnection events.Expanded App Port Handling:
ATAK_PLUGIN,PRIVATE_APP,DETECTION_SENSOR_APP).ATAK,PRIVATE_APP, andDETECTION_SENSOR_APPare now correctly broadcast to external applications.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.