mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-10-03 00:54:34 -04:00
refactor(appfunctions): migrate to AppFunctionServiceEntryPoint (#7340)
This commit is contained in:
1 parent
9962e401ed
commit
df78aaafcd
9 files changed
+223
-208
No files matched your search
@@ -58,56 +58,18 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!--
|
||||
AppFunctions 1.0.0-alpha10 discontinued the appfunctions-service artifact whose
|
||||
manifest used to contribute these two delegate services; apps must now declare
|
||||
them (mirrors the alpha09 appfunctions-service AAR manifest, classes relocated
|
||||
to the androidx.appfunctions package).
|
||||
-->
|
||||
<!-- Generated by the AppFunctions compiler from BaseMeshtasticAppFunctionService. -->
|
||||
<service
|
||||
android:name="androidx.appfunctions.PlatformAppFunctionService"
|
||||
android:enabled="@bool/enablePlatformAppFunctionService"
|
||||
android:name="org.meshtastic.app.ai.appfunctions.MeshtasticAppFunctionService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
|
||||
tools:targetApi="36">
|
||||
<property
|
||||
android:name="android.app.appfunctions"
|
||||
android:value="app_functions.xml" />
|
||||
<property
|
||||
android:name="android.app.appfunctions.schema"
|
||||
android:value="app_functions_schema.xsd" />
|
||||
<property
|
||||
android:name="android.app.appfunctions.v2"
|
||||
android:value="app_functions_v2.xml" />
|
||||
<intent-filter>
|
||||
<action android:name="android.app.appfunctions.AppFunctionService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<!--
|
||||
tools:ignore="Instantiatable": lint can't resolve this library-provided service's superclass chain
|
||||
up to android.app.Service. ExtensionAppFunctionService extends the abstract
|
||||
ExtensionsAppFunctionService, whose base is com.android.extensions.appfunctions.AppFunctionService —
|
||||
a class supplied at runtime by the optional "com.android.extensions.appfunctions" <uses-library>
|
||||
(declared android:required="false" in the appfunctions AAR), not by any compile/lint-classpath
|
||||
artifact. Lint therefore can't prove it's a Service and false-positives. The sibling
|
||||
PlatformAppFunctionService is fine because its chain ends in the compile-SDK
|
||||
android.app.appfunctions.AppFunctionService, so the check stays active there and on our own services.
|
||||
-->
|
||||
<service
|
||||
android:name="androidx.appfunctions.ExtensionAppFunctionService"
|
||||
android:enabled="@bool/enableExtensionAppFunctionService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
|
||||
tools:ignore="Instantiatable">
|
||||
<property
|
||||
android:name="android.app.appfunctions"
|
||||
android:value="app_functions.xml" />
|
||||
<property
|
||||
android:name="android.app.appfunctions.schema"
|
||||
android:value="app_functions_schema.xsd" />
|
||||
<property
|
||||
android:name="android.app.appfunctions.v2"
|
||||
android:value="app_functions_v2.xml" />
|
||||
android:value="meshtastic_app_function_service.xml" />
|
||||
<intent-filter>
|
||||
<action android:name="android.app.appfunctions.AppFunctionService" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -16,21 +16,12 @@
|
||||
*/
|
||||
package org.meshtastic.app
|
||||
|
||||
import androidx.appfunctions.AppFunctionConfiguration
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.java.KoinJavaComponent.getKoin
|
||||
import org.meshtastic.app.ai.appfunctions.AppFunctionStateSync
|
||||
import org.meshtastic.app.ai.appfunctions.MeshtasticAppFunctions
|
||||
|
||||
/**
|
||||
* Google flavor Application subclass that configures App Functions.
|
||||
*
|
||||
* Registers a custom factory so the AppFunctions runtime can instantiate [MeshtasticAppFunctions] with its Koin-managed
|
||||
* dependencies.
|
||||
*/
|
||||
class GoogleMeshUtilApplication :
|
||||
MeshUtilApplication(),
|
||||
AppFunctionConfiguration.Provider {
|
||||
/** Google flavor Application subclass that starts the App Functions enabled-state sync. */
|
||||
class GoogleMeshUtilApplication : MeshUtilApplication() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@@ -40,12 +31,4 @@ class GoogleMeshUtilApplication :
|
||||
// Off-main: construction forces the AppFunctionsPrefs subgraph and fires AppSearch binder calls.
|
||||
applicationScope.launch { getKoin().get<AppFunctionStateSync>() }
|
||||
}
|
||||
|
||||
override val appFunctionConfiguration: AppFunctionConfiguration
|
||||
get() =
|
||||
AppFunctionConfiguration.Builder()
|
||||
.addEnclosingClassFactory(MeshtasticAppFunctions::class.java) {
|
||||
getKoin().get<MeshtasticAppFunctions>()
|
||||
}
|
||||
.build()
|
||||
}
|
||||
+2
-1
@@ -134,7 +134,8 @@ class AppFunctionStateSync(
|
||||
): List<Pair<String, Boolean>> =
|
||||
if (actual == null) desired else desired.filter { (id, enabled) -> actual[id] != enabled }
|
||||
|
||||
private const val CLASS_PREFIX = "org.meshtastic.app.ai.appfunctions.MeshtasticAppFunctions#"
|
||||
// Mirrors the generated MeshtasticAppFunctionService.FUNCTION_ID_* constants, which are API 36 only.
|
||||
private const val CLASS_PREFIX = "org.meshtastic.app.ai.appfunctions.BaseMeshtasticAppFunctionService#"
|
||||
|
||||
const val SEND_MESSAGE_ID = "${CLASS_PREFIX}sendMessage"
|
||||
const val GET_MESH_STATUS_ID = "${CLASS_PREFIX}getMeshStatus"
|
||||
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.app.ai.appfunctions
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appfunctions.AppFunction
|
||||
import androidx.appfunctions.AppFunctionIntValueConstraint
|
||||
import androidx.appfunctions.AppFunctionService
|
||||
import androidx.appfunctions.AppFunctionServiceEntryPoint
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.meshtastic.core.data.ai.AiFunctionProvider
|
||||
|
||||
/**
|
||||
* Exposes Meshtastic mesh networking capabilities to system AI assistants via the Android App Functions API. Functions
|
||||
* declared here are discoverable by the system and can be invoked by AI agents such as Gemini.
|
||||
*
|
||||
* KSP generates the concrete [MeshtasticAppFunctionService] and its `meshtastic_app_function_service.xml` asset; the
|
||||
* KDoc on each function is the description agents read.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.BAKLAVA)
|
||||
@AppFunctionServiceEntryPoint(
|
||||
serviceName = "MeshtasticAppFunctionService",
|
||||
appFunctionXmlFileName = "meshtastic_app_function_service",
|
||||
)
|
||||
abstract class BaseMeshtasticAppFunctionService : AppFunctionService() {
|
||||
|
||||
private val appFunctions: MeshtasticAppFunctions by inject()
|
||||
|
||||
/**
|
||||
* Send a text message over the Meshtastic mesh radio network.
|
||||
*
|
||||
* Messages are transmitted to nearby mesh nodes using LoRa radio. The mesh network is ideal for off-grid
|
||||
* communications where cellular service is unavailable.
|
||||
*
|
||||
* @param text The message text to send (max 228 UTF-8 bytes — the mesh payload left after protobuf framing).
|
||||
* @param recipientName Optional name of a specific node to send a direct message to. If omitted, the message is
|
||||
* broadcast to all nodes on the specified channel.
|
||||
* @param channelName Optional channel name to broadcast on. If omitted, uses the primary channel. Ignored when
|
||||
* recipientName is specified.
|
||||
* @return A [SendMessageResponse] with the message ID, channel, and timestamp.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun sendMessage(
|
||||
text: String,
|
||||
recipientName: String? = null,
|
||||
channelName: String? = null,
|
||||
): SendMessageResponse = appFunctions.sendMessage(text, recipientName, channelName)
|
||||
|
||||
/**
|
||||
* Get the current status of the Meshtastic mesh network.
|
||||
*
|
||||
* Returns connection state, number of online nodes, total known nodes, the connected device's battery level, and
|
||||
* the local node name.
|
||||
*
|
||||
* @return A [MeshStatusResponse] with the current mesh network status.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getMeshStatus(): MeshStatusResponse = appFunctions.getMeshStatus()
|
||||
|
||||
/**
|
||||
* List all nodes currently visible on the Meshtastic mesh network.
|
||||
*
|
||||
* Returns detailed information about each node including name, battery level, and last heard time. Nodes are sorted
|
||||
* by most recently heard first.
|
||||
*
|
||||
* @return A list of nodes with their current status and metrics.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getNodeList(): GetNodeListResponse = appFunctions.getNodeList()
|
||||
|
||||
/**
|
||||
* List all available Meshtastic mesh channels and their configurations.
|
||||
*
|
||||
* Returns details about each channel including name, index, primary status, and uplink/downlink settings.
|
||||
*
|
||||
* @return A list of channels with their current configuration.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getChannelInfo(): GetChannelInfoResponse = appFunctions.getChannelInfo()
|
||||
|
||||
/**
|
||||
* Get the status and metrics of the local Meshtastic radio device.
|
||||
*
|
||||
* Returns hardware model, firmware version, battery level, charging status, and current radio state.
|
||||
*
|
||||
* @return Device status with current metrics and configuration.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getDeviceStatus(): GetDeviceStatusResponse = appFunctions.getDeviceStatus()
|
||||
|
||||
/**
|
||||
* Retrieve detailed telemetry and status for a specific mesh node.
|
||||
*
|
||||
* Returns per-node metrics including battery level, signal strength, hardware model, and location data.
|
||||
*
|
||||
* @param nodeId The target node ID (e.g., '!abc12345' or user ID).
|
||||
* @return A [GetNodeDetailsResponse] with detailed node information.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getNodeDetails(nodeId: String): GetNodeDetailsResponse = appFunctions.getNodeDetails(nodeId)
|
||||
|
||||
/**
|
||||
* Retrieve aggregate network metrics and statistics for the entire mesh.
|
||||
*
|
||||
* Returns mesh-wide analytics including total node count, online nodes, average battery level, and health score.
|
||||
*
|
||||
* @return A [GetMeshMetricsResponse] with mesh-wide statistics.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getMeshMetrics(): GetMeshMetricsResponse = appFunctions.getMeshMetrics()
|
||||
|
||||
/**
|
||||
* Retrieve recent messages received over the Meshtastic mesh radio network.
|
||||
*
|
||||
* Returns a list of recent messages from the local message history. Messages are stored locally and do not require
|
||||
* an active mesh connection. Useful for catching up on conversations or reviewing recent communications.
|
||||
*
|
||||
* @param contactName Optional name of a node or channel to filter messages from. If omitted, returns messages from
|
||||
* all contacts sorted by most recent.
|
||||
* @param limit Maximum number of messages to return: 1, 5, 10, 20 or 50. Defaults to 20.
|
||||
* @return A [GetRecentMessagesResponse] containing the list of recent messages.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getRecentMessages(
|
||||
contactName: String? = null,
|
||||
@AppFunctionIntValueConstraint(enumValues = [1, 5, 10, 20, 50])
|
||||
limit: Int = AiFunctionProvider.DEFAULT_MESSAGE_LIMIT,
|
||||
): GetRecentMessagesResponse = appFunctions.getRecentMessages(contactName, limit)
|
||||
|
||||
/**
|
||||
* Get a summary of unread messages across all Meshtastic mesh contacts.
|
||||
*
|
||||
* Returns the total unread count and a per-contact breakdown showing who sent unread messages, how many are unread,
|
||||
* and a preview of the last message. Muted contacts are excluded. Does not require an active mesh connection.
|
||||
*
|
||||
* @return A [GetUnreadSummaryResponse] with the total unread count and per-contact details.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getUnreadSummary(): GetUnreadSummaryResponse = appFunctions.getUnreadSummary()
|
||||
}
|
||||
+11
-122
@@ -16,10 +16,7 @@
|
||||
*/
|
||||
package org.meshtastic.app.ai.appfunctions
|
||||
|
||||
import androidx.appfunctions.AppFunction
|
||||
import androidx.appfunctions.AppFunctionContext
|
||||
import androidx.appfunctions.AppFunctionElementNotFoundException
|
||||
import androidx.appfunctions.AppFunctionIntValueConstraint
|
||||
import androidx.appfunctions.AppFunctionInvalidArgumentException
|
||||
import androidx.appfunctions.AppFunctionNotSupportedException
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
@@ -27,35 +24,12 @@ import org.meshtastic.core.data.ai.AiFunctionProvider
|
||||
import org.meshtastic.core.data.ai.SendMessageResult
|
||||
|
||||
/**
|
||||
* Exposes Meshtastic mesh networking capabilities to system AI assistants via the Android App Functions API. Functions
|
||||
* declared here are discoverable by the system and can be invoked by AI agents such as Gemini.
|
||||
* Maps [AiFunctionProvider] results to App Functions responses and exceptions. [BaseMeshtasticAppFunctionService]
|
||||
* declares the functions agents see and delegates each one here.
|
||||
*/
|
||||
// The AppFunctions calling convention requires every @AppFunction to take AppFunctionContext as
|
||||
// its first parameter, even when the implementation never reads it.
|
||||
@Suppress("UnusedParameter")
|
||||
class MeshtasticAppFunctions(private val provider: AiFunctionProvider) {
|
||||
|
||||
/**
|
||||
* Send a text message over the Meshtastic mesh radio network.
|
||||
*
|
||||
* Messages are transmitted to nearby mesh nodes using LoRa radio. The mesh network is ideal for off-grid
|
||||
* communications where cellular service is unavailable.
|
||||
*
|
||||
* @param context The app function invocation context provided by the system.
|
||||
* @param text The message text to send (max 228 UTF-8 bytes — the mesh payload left after protobuf framing).
|
||||
* @param recipientName Optional name of a specific node to send a direct message to. If omitted, the message is
|
||||
* broadcast to all nodes on the specified channel.
|
||||
* @param channelName Optional channel name to broadcast on. If omitted, uses the primary channel. Ignored when
|
||||
* recipientName is specified.
|
||||
* @return A [SendMessageResponse] with the message ID, channel, and timestamp.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun sendMessage(
|
||||
context: AppFunctionContext,
|
||||
text: String,
|
||||
recipientName: String? = null,
|
||||
channelName: String? = null,
|
||||
): SendMessageResponse {
|
||||
suspend fun sendMessage(text: String, recipientName: String?, channelName: String?): SendMessageResponse {
|
||||
val result =
|
||||
try {
|
||||
provider.sendMessage(text, recipientName, channelName)
|
||||
@@ -91,17 +65,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current status of the Meshtastic mesh network.
|
||||
*
|
||||
* Returns connection state, number of online nodes, total known nodes, the connected device's battery level, and
|
||||
* the local node name.
|
||||
*
|
||||
* @param context The app function invocation context provided by the system.
|
||||
* @return A [MeshStatusResponse] with the current mesh network status.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getMeshStatus(context: AppFunctionContext): MeshStatusResponse {
|
||||
suspend fun getMeshStatus(): MeshStatusResponse {
|
||||
val status =
|
||||
try {
|
||||
provider.getMeshStatus()
|
||||
@@ -120,17 +84,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* List all nodes currently visible on the Meshtastic mesh network.
|
||||
*
|
||||
* Returns detailed information about each node including name, battery level, and last heard time. Nodes are sorted
|
||||
* by most recently heard first.
|
||||
*
|
||||
* @param context The app function invocation context provided by the system.
|
||||
* @return A list of nodes with their current status and metrics.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getNodeList(context: AppFunctionContext): GetNodeListResponse {
|
||||
suspend fun getNodeList(): GetNodeListResponse {
|
||||
val result =
|
||||
try {
|
||||
provider.getNodeList()
|
||||
@@ -163,16 +117,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available Meshtastic mesh channels and their configurations.
|
||||
*
|
||||
* Returns details about each channel including name, index, primary status, and uplink/downlink settings.
|
||||
*
|
||||
* @param context The app function invocation context provided by the system.
|
||||
* @return A list of channels with their current configuration.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getChannelInfo(context: AppFunctionContext): GetChannelInfoResponse {
|
||||
suspend fun getChannelInfo(): GetChannelInfoResponse {
|
||||
val result =
|
||||
try {
|
||||
provider.getChannelInfo()
|
||||
@@ -205,16 +150,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status and metrics of the local Meshtastic radio device.
|
||||
*
|
||||
* Returns hardware model, firmware version, battery level, charging status, and current radio state.
|
||||
*
|
||||
* @param context The app function invocation context provided by the system.
|
||||
* @return Device status with current metrics and configuration.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getDeviceStatus(context: AppFunctionContext): GetDeviceStatusResponse {
|
||||
suspend fun getDeviceStatus(): GetDeviceStatusResponse {
|
||||
val result =
|
||||
try {
|
||||
provider.getDeviceStatus()
|
||||
@@ -243,17 +179,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve detailed telemetry and status for a specific mesh node.
|
||||
*
|
||||
* Returns per-node metrics including battery level, signal strength, hardware model, and location data.
|
||||
*
|
||||
* @param context The app function invocation context provided by the system.
|
||||
* @param nodeId The target node ID (e.g., '!abc12345' or user ID).
|
||||
* @return A [GetNodeDetailsResponse] with detailed node information.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getNodeDetails(context: AppFunctionContext, nodeId: String): GetNodeDetailsResponse {
|
||||
suspend fun getNodeDetails(nodeId: String): GetNodeDetailsResponse {
|
||||
val result =
|
||||
try {
|
||||
provider.getNodeDetails(nodeId)
|
||||
@@ -294,16 +220,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve aggregate network metrics and statistics for the entire mesh.
|
||||
*
|
||||
* Returns mesh-wide analytics including total node count, online nodes, average battery level, and health score.
|
||||
*
|
||||
* @param context The app function invocation context provided by the system.
|
||||
* @return A [GetMeshMetricsResponse] with mesh-wide statistics.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getMeshMetrics(context: AppFunctionContext): GetMeshMetricsResponse {
|
||||
suspend fun getMeshMetrics(): GetMeshMetricsResponse {
|
||||
val result =
|
||||
try {
|
||||
provider.getMeshMetrics()
|
||||
@@ -332,25 +249,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve recent messages received over the Meshtastic mesh radio network.
|
||||
*
|
||||
* Returns a list of recent messages from the local message history. Messages are stored locally and do not require
|
||||
* an active mesh connection. Useful for catching up on conversations or reviewing recent communications.
|
||||
*
|
||||
* @param context The app function invocation context provided by the system.
|
||||
* @param contactName Optional name of a node or channel to filter messages from. If omitted, returns messages from
|
||||
* all contacts sorted by most recent.
|
||||
* @param limit Maximum number of messages to return (1–50). Defaults to 20.
|
||||
* @return A [GetRecentMessagesResponse] containing the list of recent messages.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getRecentMessages(
|
||||
context: AppFunctionContext,
|
||||
contactName: String? = null,
|
||||
@AppFunctionIntValueConstraint(enumValues = [1, 5, 10, 20, 50])
|
||||
limit: Int = AiFunctionProvider.DEFAULT_MESSAGE_LIMIT,
|
||||
): GetRecentMessagesResponse {
|
||||
suspend fun getRecentMessages(contactName: String?, limit: Int): GetRecentMessagesResponse {
|
||||
val result =
|
||||
try {
|
||||
provider.getRecentMessages(contactName, limit)
|
||||
@@ -381,17 +280,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of unread messages across all Meshtastic mesh contacts.
|
||||
*
|
||||
* Returns the total unread count and a per-contact breakdown showing who sent unread messages, how many are unread,
|
||||
* and a preview of the last message. Muted contacts are excluded. Does not require an active mesh connection.
|
||||
*
|
||||
* @param context The app function invocation context provided by the system.
|
||||
* @return A [GetUnreadSummaryResponse] with the total unread count and per-contact details.
|
||||
*/
|
||||
@AppFunction(isDescribedByKDoc = true)
|
||||
suspend fun getUnreadSummary(context: AppFunctionContext): GetUnreadSummaryResponse {
|
||||
suspend fun getUnreadSummary(): GetUnreadSummaryResponse {
|
||||
val result =
|
||||
try {
|
||||
provider.getUnreadSummary()
|
||||
|
||||
+31
@@ -51,4 +51,35 @@ class AppFunctionStateSyncTest {
|
||||
|
||||
assertEquals(listOf(AppFunctionStateSync.GET_NODE_LIST_ID to false), pendingWrites(desired, actual))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toggle ids match the generated function ids`() {
|
||||
assertEquals(MeshtasticAppFunctionService.FUNCTION_ID_SEND_MESSAGE, AppFunctionStateSync.SEND_MESSAGE_ID)
|
||||
assertEquals(MeshtasticAppFunctionService.FUNCTION_ID_GET_MESH_STATUS, AppFunctionStateSync.GET_MESH_STATUS_ID)
|
||||
assertEquals(MeshtasticAppFunctionService.FUNCTION_ID_GET_NODE_LIST, AppFunctionStateSync.GET_NODE_LIST_ID)
|
||||
assertEquals(
|
||||
MeshtasticAppFunctionService.FUNCTION_ID_GET_CHANNEL_INFO,
|
||||
AppFunctionStateSync.GET_CHANNEL_INFO_ID,
|
||||
)
|
||||
assertEquals(
|
||||
MeshtasticAppFunctionService.FUNCTION_ID_GET_DEVICE_STATUS,
|
||||
AppFunctionStateSync.GET_DEVICE_STATUS_ID,
|
||||
)
|
||||
assertEquals(
|
||||
MeshtasticAppFunctionService.FUNCTION_ID_GET_NODE_DETAILS,
|
||||
AppFunctionStateSync.GET_NODE_DETAILS_ID,
|
||||
)
|
||||
assertEquals(
|
||||
MeshtasticAppFunctionService.FUNCTION_ID_GET_MESH_METRICS,
|
||||
AppFunctionStateSync.GET_MESH_METRICS_ID,
|
||||
)
|
||||
assertEquals(
|
||||
MeshtasticAppFunctionService.FUNCTION_ID_GET_RECENT_MESSAGES,
|
||||
AppFunctionStateSync.GET_RECENT_MESSAGES_ID,
|
||||
)
|
||||
assertEquals(
|
||||
MeshtasticAppFunctionService.FUNCTION_ID_GET_UNREAD_SUMMARY,
|
||||
AppFunctionStateSync.GET_UNREAD_SUMMARY_ID,
|
||||
)
|
||||
}
|
||||
}
|
||||
+12
-16
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package org.meshtastic.app.ai.appfunctions
|
||||
|
||||
import androidx.appfunctions.AppFunctionContext
|
||||
import androidx.appfunctions.AppFunctionElementNotFoundException
|
||||
import androidx.appfunctions.AppFunctionInvalidArgumentException
|
||||
import androidx.appfunctions.AppFunctionNotSupportedException
|
||||
@@ -56,7 +55,6 @@ import kotlin.test.assertTrue
|
||||
class MeshtasticAppFunctionsTest {
|
||||
|
||||
private val provider: AiFunctionProvider = mock(MockMode.autofill)
|
||||
private val context: AppFunctionContext = mock(MockMode.autofill)
|
||||
private val appFunctions = MeshtasticAppFunctions(provider)
|
||||
|
||||
@Test
|
||||
@@ -64,7 +62,7 @@ class MeshtasticAppFunctionsTest {
|
||||
everySuspend { provider.sendMessage("Hello", "Alice", null) } returns
|
||||
SendMessageResult.Success(messageId = 1234, channel = "Primary", timestamp = 1700000000L)
|
||||
|
||||
val response = appFunctions.sendMessage(context, "Hello", "Alice", null)
|
||||
val response = appFunctions.sendMessage("Hello", "Alice", null)
|
||||
|
||||
assertEquals(1234, response.messageId)
|
||||
assertEquals("Primary", response.channel)
|
||||
@@ -77,9 +75,7 @@ class MeshtasticAppFunctionsTest {
|
||||
SendMessageResult.AmbiguousName(listOf("Alice", "Albert"))
|
||||
|
||||
val exception =
|
||||
assertFailsWith<AppFunctionInvalidArgumentException> {
|
||||
appFunctions.sendMessage(context, "Hello", "Al", null)
|
||||
}
|
||||
assertFailsWith<AppFunctionInvalidArgumentException> { appFunctions.sendMessage("Hello", "Al", null) }
|
||||
assertTrue(exception.message!!.contains("Multiple nodes match that name"))
|
||||
}
|
||||
|
||||
@@ -88,7 +84,7 @@ class MeshtasticAppFunctionsTest {
|
||||
everySuspend { provider.sendMessage("Hello", "Alice", null) } returns
|
||||
SendMessageResult.NotConnected("Not connected")
|
||||
|
||||
assertFailsWith<AppFunctionNotSupportedException> { appFunctions.sendMessage(context, "Hello", "Alice", null) }
|
||||
assertFailsWith<AppFunctionNotSupportedException> { appFunctions.sendMessage("Hello", "Alice", null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -102,7 +98,7 @@ class MeshtasticAppFunctionsTest {
|
||||
localNodeName = "MyNode",
|
||||
)
|
||||
|
||||
val response = appFunctions.getMeshStatus(context)
|
||||
val response = appFunctions.getMeshStatus()
|
||||
|
||||
assertEquals("CONNECTED", response.connectionState)
|
||||
assertEquals(5, response.onlineNodeCount)
|
||||
@@ -120,7 +116,7 @@ class MeshtasticAppFunctionsTest {
|
||||
)
|
||||
everySuspend { provider.getNodeList() } returns GetNodeListResult.Success(nodes)
|
||||
|
||||
val response = appFunctions.getNodeList(context)
|
||||
val response = appFunctions.getNodeList()
|
||||
|
||||
assertEquals(2, response.nodes.size)
|
||||
assertEquals("1", response.nodes[0].id)
|
||||
@@ -152,7 +148,7 @@ class MeshtasticAppFunctionsTest {
|
||||
)
|
||||
everySuspend { provider.getChannelInfo() } returns GetChannelInfoResult.Success(channels)
|
||||
|
||||
val response = appFunctions.getChannelInfo(context)
|
||||
val response = appFunctions.getChannelInfo()
|
||||
|
||||
assertEquals(2, response.channels.size)
|
||||
assertEquals("Primary", response.channels[0].name)
|
||||
@@ -174,7 +170,7 @@ class MeshtasticAppFunctionsTest {
|
||||
)
|
||||
everySuspend { provider.getDeviceStatus() } returns GetDeviceStatusResult.Success(device)
|
||||
|
||||
val response = appFunctions.getDeviceStatus(context)
|
||||
val response = appFunctions.getDeviceStatus()
|
||||
|
||||
assertEquals("T-Beam", response.model)
|
||||
assertEquals("2.3.15", response.firmwareVersion)
|
||||
@@ -207,7 +203,7 @@ class MeshtasticAppFunctionsTest {
|
||||
)
|
||||
everySuspend { provider.getNodeDetails("!abc12345") } returns GetNodeDetailsResult.Success(nodeDetails)
|
||||
|
||||
val response = appFunctions.getNodeDetails(context, "!abc12345")
|
||||
val response = appFunctions.getNodeDetails("!abc12345")
|
||||
|
||||
assertEquals("!abc12345", response.id)
|
||||
assertEquals("TestNode", response.name)
|
||||
@@ -220,7 +216,7 @@ class MeshtasticAppFunctionsTest {
|
||||
fun getNodeDetails_notFound() = runTest {
|
||||
everySuspend { provider.getNodeDetails("!unknown") } returns GetNodeDetailsResult.NotFound("Node not found")
|
||||
|
||||
assertFailsWith<AppFunctionElementNotFoundException> { appFunctions.getNodeDetails(context, "!unknown") }
|
||||
assertFailsWith<AppFunctionElementNotFoundException> { appFunctions.getNodeDetails("!unknown") }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -237,7 +233,7 @@ class MeshtasticAppFunctionsTest {
|
||||
)
|
||||
everySuspend { provider.getMeshMetrics() } returns GetMeshMetricsResult.Success(metrics)
|
||||
|
||||
val response = appFunctions.getMeshMetrics(context)
|
||||
val response = appFunctions.getMeshMetrics()
|
||||
|
||||
assertEquals(12, response.totalNodeCount)
|
||||
assertEquals(4, response.onlineNodeCount)
|
||||
@@ -260,7 +256,7 @@ class MeshtasticAppFunctionsTest {
|
||||
)
|
||||
everySuspend { provider.getRecentMessages(null, 10) } returns GetRecentMessagesResult.Success(messages)
|
||||
|
||||
val response = appFunctions.getRecentMessages(context, null, 10)
|
||||
val response = appFunctions.getRecentMessages(null, 10)
|
||||
|
||||
assertEquals(1, response.messages.size)
|
||||
assertEquals("Alice", response.messages[0].senderName)
|
||||
@@ -284,7 +280,7 @@ class MeshtasticAppFunctionsTest {
|
||||
)
|
||||
everySuspend { provider.getUnreadSummary() } returns GetUnreadSummaryResult.Success(summary)
|
||||
|
||||
val response = appFunctions.getUnreadSummary(context)
|
||||
val response = appFunctions.getUnreadSummary()
|
||||
|
||||
assertEquals(3, response.totalUnreadCount)
|
||||
assertEquals(1, response.contacts.size)
|
||||
|
||||
Reference in new issue
Block a user