Skip to content

feat: Add disconnect broadcast and improve app port handling#4502

Merged
jamesarich merged 7 commits into
mainfrom
fix/aidl2
Feb 7, 2026
Merged

feat: Add disconnect broadcast and improve app port handling#4502
jamesarich merged 7 commits into
mainfrom
fix/aidl2

Conversation

@jamesarich

@jamesarich jamesarich commented Feb 7, 2026

Copy link
Copy Markdown
Collaborator

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.

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>
Copilot AI review requested due to automatic review settings February 7, 2026 21:19
@github-actions github-actions Bot added the bugfix PR tag label Feb 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_DISCONNECTED broadcast when the connection state is Disconnected.
  • Emit an additional “numeric port” RECEIVED.<portNum> broadcast for backwards compatibility when receiving data.
  • Expand MeshDataHandler port 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.

Comment on lines +199 to +201
// By default, if we don't know what it is, we should probably broadcast it
// so that external apps can handle it.
shouldBroadcast = true

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
// 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.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +35
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"

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Suggested change
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"

Copilot uses AI. Check for mistakes.
Comment on lines 50 to +58
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))
}

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +95 to +97
if (connectionState == ConnectionState.Disconnected) {
explicitBroadcast(Intent(ACTION_MESH_DISCONNECTED))
}

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +184 to 202
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
}

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot generated this review using guidance from repository custom instructions.
* 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.

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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").

Suggested change
* because it implies we have assembled a warehouse valid node db.
* because it implies we have assembled a valid node db.

Copilot uses AI. Check for mistakes.
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

codecov Bot commented Feb 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 18.75000% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 11.05%. Comparing base (e94e4da) to head (c9082b8).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...htastic/android/meshserviceexample/MainActivity.kt 0.00% 13 Missing ⚠️
...m/geeksville/mesh/service/MeshServiceBroadcasts.kt 12.50% 6 Missing and 1 partial ⚠️
...ava/com/geeksville/mesh/service/MeshDataHandler.kt 37.50% 5 Missing ⚠️
...m/geeksville/mesh/service/MeshConnectionManager.kt 66.66% 0 Missing and 1 partial ⚠️
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.
📢 Have feedback on the report? Share it here.

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>
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
@jamesarich jamesarich added this pull request to the merge queue Feb 7, 2026
Merged via the queue into main with commit 31790ff Feb 7, 2026
7 of 8 checks passed
@jamesarich jamesarich deleted the fix/aidl2 branch February 7, 2026 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants