Add :core:service (#3253)

This commit is contained in:
Phil Oliver 2025-09-30 16:55:56 -04:00 committed by GitHub
parent cf59033c49
commit db2ef75e08
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 125 additions and 1077 deletions

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2025 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/>.
*/
plugins {
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.hilt)
}
android {
buildFeatures { aidl = true }
namespace = "org.meshtastic.core.service"
}
dependencies {
implementation(projects.core.database)
implementation(projects.core.model)
implementation(projects.core.proto)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.timber)
}

View file

@ -0,0 +1,173 @@
package org.meshtastic.core.service;
// Declare any non-default types here with import statements
import org.meshtastic.core.model.DataPacket;
import org.meshtastic.core.model.NodeInfo;
import org.meshtastic.core.model.MeshUser;
import org.meshtastic.core.model.Position;
import org.meshtastic.core.model.MyNodeInfo;
/**
This is the public android API for talking to meshtastic radios.
To connect to meshtastic you should bind to it per https://developer.android.com/guide/components/bound-services
The intent you use to reach the service should look like this:
val intent = Intent().apply {
setClassName(
"com.geeksville.mesh",
"com.geeksville.mesh.service.MeshService"
)
}
In Android 11+ you *may* need to add the following to the client app's manifest to allow binding of the mesh service:
<queries>
<package android:name="com.geeksville.mesh" />
</queries>
For additional information, see https://developer.android.com/guide/topics/manifest/queries-element
Once you have bound to the service you should register your broadcast receivers per https://developer.android.com/guide/components/broadcasts#context-registered-receivers
// com.geeksville.mesh.x broadcast intents, where x is:
// RECEIVED.<portnumm> - will **only** deliver packets for the specified port number. If a wellknown portnums.proto name for portnum is known it will be used
// (i.e. com.geeksville.mesh.RECEIVED.TEXT_MESSAGE_APP) else the numeric portnum will be included as a base 10 integer (com.geeksville.mesh.RECEIVED.4403 etc...)
// NODE_CHANGE for new IDs appearing or disappearing
// CONNECTION_CHANGED for losing/gaining connection to the packet radio
// MESSAGE_STATUS_CHANGED for any message status changes (for sent messages only, payload will contain a message ID and a MessageStatus)
Note - these calls might throw RemoteException to indicate mesh error states
*/
interface IMeshService {
/// Tell the service where to send its broadcasts of received packets
/// This call is only required for manifest declared receivers. If your receiver is context-registered
/// you don't need this.
void subscribeReceiver(String packageName, String receiverName);
/**
* Set the user info for this node
*/
void setOwner(in MeshUser user);
void setRemoteOwner(in int requestId, in byte []payload);
void getRemoteOwner(in int requestId, in int destNum);
/// Return my unique user ID string
String getMyId();
/// Return a unique packet ID
int getPacketId();
/*
Send a packet to a specified node name
typ is defined in mesh.proto Data.Type. For now juse use 0 to mean opaque bytes.
destId can be null to indicate "broadcast message"
messageStatus and id of the provided message will be updated by this routine to indicate
message send status and the ID that can be used to locate the message in the future
*/
void send(inout DataPacket packet);
/**
Get the IDs of everyone on the mesh. You should also subscribe for NODE_CHANGE broadcasts.
*/
List<NodeInfo> getNodes();
/// This method is only intended for use in our GUI, so the user can set radio options
/// It returns a DeviceConfig protobuf.
byte []getConfig();
/// It sets a Config protobuf via admin packet
void setConfig(in byte []payload);
/// Set and get a Config protobuf via admin packet
void setRemoteConfig(in int requestId, in int destNum, in byte []payload);
void getRemoteConfig(in int requestId, in int destNum, in int configTypeValue);
/// Set and get a ModuleConfig protobuf via admin packet
void setModuleConfig(in int requestId, in int destNum, in byte []payload);
void getModuleConfig(in int requestId, in int destNum, in int moduleConfigTypeValue);
/// Set and get the Ext Notification Ringtone string via admin packet
void setRingtone(in int destNum, in String ringtone);
void getRingtone(in int requestId, in int destNum);
/// Set and get the Canned Message Messages string via admin packet
void setCannedMessages(in int destNum, in String messages);
void getCannedMessages(in int requestId, in int destNum);
/// This method is only intended for use in our GUI, so the user can set radio options
/// It sets a Channel protobuf via admin packet
void setChannel(in byte []payload);
/// Set and get a Channel protobuf via admin packet
void setRemoteChannel(in int requestId, in int destNum, in byte []payload);
void getRemoteChannel(in int requestId, in int destNum, in int channelIndex);
/// Send beginEditSettings admin packet to nodeNum
void beginEditSettings();
/// Send commitEditSettings admin packet to nodeNum
void commitEditSettings();
/// delete a specific nodeNum from nodeDB
void removeByNodenum(in int requestID, in int nodeNum);
/// Send position packet with wantResponse to nodeNum
void requestPosition(in int destNum, in Position position);
/// Send setFixedPosition admin packet (or removeFixedPosition if Position is empty)
void setFixedPosition(in int destNum, in Position position);
/// Send traceroute packet with wantResponse to nodeNum
void requestTraceroute(in int requestId, in int destNum);
/// Send Shutdown admin packet to nodeNum
void requestShutdown(in int requestId, in int destNum);
/// Send Reboot admin packet to nodeNum
void requestReboot(in int requestId, in int destNum);
/// Send FactoryReset admin packet to nodeNum
void requestFactoryReset(in int requestId, in int destNum);
/// Send NodedbReset admin packet to nodeNum
void requestNodedbReset(in int requestId, in int destNum);
/// Returns a ChannelSet protobuf
byte []getChannelSet();
/**
Is the packet radio currently connected to the phone? Returns a ConnectionState string.
*/
String connectionState();
/// If a macaddress we will try to talk to our device, if null we will be idle.
/// Any current connection will be dropped (even if the device address is the same) before reconnecting.
/// Users should not call this directly, only used internally by the MeshUtil activity
/// Returns true if the device address actually changed, or false if no change was needed
boolean setDeviceAddress(String deviceAddr);
/// Get basic device hardware info about our connected radio. Will never return NULL. Will return NULL
/// if no my node info is available (i.e. it will not throw an exception)
MyNodeInfo getMyNodeInfo();
/// Start updating the radios firmware
void startFirmwareUpdate();
/// Return a number 0-100 for firmware update progress. -1 for completed and success, -2 for failure
int getUpdateStatus();
/// Start providing location (from phone GPS) to mesh
void startProvideLocation();
/// Stop providing location (from phone GPS) to mesh
void stopProvideLocation();
/// Send request for node UserInfo
void requestUserInfo(in int destNum);
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2025 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.service
enum class ConnectionState {
/** We are disconnected from the device, and we should be trying to reconnect. */
DISCONNECTED,
/** We are connected to the device and communicating normally. */
CONNECTED,
/** The device is in a light sleep state, and we are waiting for it to wake up and reconnect to us. */
DEVICE_SLEEP,
;
fun isConnected() = this != DISCONNECTED
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2025 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.service
import com.geeksville.mesh.AdminProtos
import org.meshtastic.core.database.model.Node
sealed class ServiceAction {
data class GetDeviceMetadata(val destNum: Int) : ServiceAction()
data class Favorite(val node: Node) : ServiceAction()
data class Ignore(val node: Node) : ServiceAction()
data class Reaction(val emoji: String, val replyId: Int, val contactKey: String) : ServiceAction()
data class AddSharedContact(val contact: AdminProtos.SharedContact) : ServiceAction()
}

View file

@ -0,0 +1,124 @@
/*
* Copyright (c) 2025 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.service
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.MeshProtos.MeshPacket
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
/** Repository class for managing the [IMeshService] instance and connection state */
@Suppress("TooManyFunctions")
@Singleton
class ServiceRepository @Inject constructor() {
var meshService: IMeshService? = null
private set
fun setMeshService(service: IMeshService?) {
meshService = service
}
// Connection state to our radio device
private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED)
val connectionState: StateFlow<ConnectionState>
get() = _connectionState
fun setConnectionState(connectionState: ConnectionState) {
_connectionState.value = connectionState
}
// Current bluetooth link RSSI (dBm). Null if not connected or not a bluetooth interface.
private val _bluetoothRssi = MutableStateFlow<Int?>(null)
val bluetoothRssi: StateFlow<Int?>
get() = _bluetoothRssi
fun setBluetoothRssi(rssi: Int?) {
_bluetoothRssi.value = rssi
}
private val _clientNotification = MutableStateFlow<MeshProtos.ClientNotification?>(null)
val clientNotification: StateFlow<MeshProtos.ClientNotification?>
get() = _clientNotification
fun setClientNotification(notification: MeshProtos.ClientNotification?) {
Timber.e(notification?.message.orEmpty())
_clientNotification.value = notification
}
fun clearClientNotification() {
_clientNotification.value = null
}
private val _errorMessage = MutableStateFlow<String?>(null)
val errorMessage: StateFlow<String?>
get() = _errorMessage
fun setErrorMessage(text: String) {
Timber.e(text)
_errorMessage.value = text
}
fun clearErrorMessage() {
_errorMessage.value = null
}
private val _statusMessage = MutableStateFlow<String?>(null)
val statusMessage: StateFlow<String?>
get() = _statusMessage
fun setStatusMessage(text: String) {
if (connectionState.value != ConnectionState.CONNECTED) {
_statusMessage.value = text
}
}
private val _meshPacketFlow = MutableSharedFlow<MeshPacket>()
val meshPacketFlow: SharedFlow<MeshPacket>
get() = _meshPacketFlow
suspend fun emitMeshPacket(packet: MeshPacket) {
_meshPacketFlow.emit(packet)
}
private val _tracerouteResponse = MutableStateFlow<String?>(null)
val tracerouteResponse: StateFlow<String?>
get() = _tracerouteResponse
fun setTracerouteResponse(value: String?) {
_tracerouteResponse.value = value
}
fun clearTracerouteResponse() {
setTracerouteResponse(null)
}
private val _serviceAction = Channel<ServiceAction>()
val serviceAction = _serviceAction.receiveAsFlow()
suspend fun onServiceAction(action: ServiceAction) {
_serviceAction.send(action)
}
}