mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
309 lines
12 KiB
Kotlin
309 lines
12 KiB
Kotlin
/*
|
|
* 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 com.geeksville.mesh.service
|
|
|
|
import com.geeksville.mesh.concurrent.handledLaunch
|
|
import com.geeksville.mesh.util.ignoreException
|
|
import dagger.Lazy
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.SupervisorJob
|
|
import org.meshtastic.core.analytics.DataPair
|
|
import org.meshtastic.core.analytics.platform.PlatformAnalytics
|
|
import org.meshtastic.core.data.repository.PacketRepository
|
|
import org.meshtastic.core.database.DatabaseManager
|
|
import org.meshtastic.core.database.entity.ReactionEntity
|
|
import org.meshtastic.core.model.DataPacket
|
|
import org.meshtastic.core.model.Position
|
|
import org.meshtastic.core.prefs.mesh.MeshPrefs
|
|
import org.meshtastic.core.service.MeshServiceNotifications
|
|
import org.meshtastic.core.service.ServiceAction
|
|
import org.meshtastic.proto.AdminProtos
|
|
import org.meshtastic.proto.ChannelProtos
|
|
import org.meshtastic.proto.ConfigProtos
|
|
import org.meshtastic.proto.MeshProtos
|
|
import org.meshtastic.proto.ModuleConfigProtos
|
|
import org.meshtastic.proto.Portnums
|
|
import org.meshtastic.proto.user
|
|
import javax.inject.Inject
|
|
import javax.inject.Singleton
|
|
|
|
@Suppress("LongParameterList", "TooManyFunctions")
|
|
@Singleton
|
|
class MeshActionHandler
|
|
@Inject
|
|
constructor(
|
|
private val nodeManager: MeshNodeManager,
|
|
private val commandSender: MeshCommandSender,
|
|
private val packetRepository: Lazy<PacketRepository>,
|
|
private val serviceBroadcasts: MeshServiceBroadcasts,
|
|
private val dataHandler: MeshDataHandler,
|
|
private val analytics: PlatformAnalytics,
|
|
private val meshPrefs: MeshPrefs,
|
|
private val databaseManager: DatabaseManager,
|
|
private val serviceNotifications: MeshServiceNotifications,
|
|
) {
|
|
private var scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
|
|
fun start(scope: CoroutineScope) {
|
|
this.scope = scope
|
|
}
|
|
|
|
companion object {
|
|
private const val DEFAULT_REBOOT_DELAY = 5
|
|
}
|
|
|
|
fun onServiceAction(action: ServiceAction) {
|
|
ignoreException {
|
|
val myNodeNum = nodeManager.myNodeNum ?: return@ignoreException
|
|
when (action) {
|
|
is ServiceAction.Favorite -> {
|
|
val node = action.node
|
|
commandSender.sendAdmin(myNodeNum) {
|
|
if (node.isFavorite) removeFavoriteNode = node.num else setFavoriteNode = node.num
|
|
}
|
|
nodeManager.updateNodeInfo(node.num) { it.isFavorite = !node.isFavorite }
|
|
}
|
|
is ServiceAction.Ignore -> {
|
|
val node = action.node
|
|
commandSender.sendAdmin(myNodeNum) {
|
|
if (node.isIgnored) removeIgnoredNode = node.num else setIgnoredNode = node.num
|
|
}
|
|
nodeManager.updateNodeInfo(node.num) { it.isIgnored = !node.isIgnored }
|
|
}
|
|
is ServiceAction.Reaction -> {
|
|
val channel = action.contactKey[0].digitToInt()
|
|
val destId = action.contactKey.substring(1)
|
|
val dataPacket =
|
|
org.meshtastic.core.model.DataPacket(
|
|
to = destId,
|
|
dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
|
|
bytes = action.emoji.encodeToByteArray(),
|
|
channel = channel,
|
|
replyId = action.replyId,
|
|
wantAck = false,
|
|
)
|
|
commandSender.sendData(dataPacket)
|
|
rememberReaction(action)
|
|
}
|
|
is ServiceAction.ImportContact -> {
|
|
val verifiedContact = action.contact.toBuilder().setManuallyVerified(true).build()
|
|
commandSender.sendAdmin(myNodeNum) { addContact = verifiedContact }
|
|
nodeManager.handleReceivedUser(
|
|
verifiedContact.nodeNum,
|
|
verifiedContact.user,
|
|
manuallyVerified = true,
|
|
)
|
|
}
|
|
is ServiceAction.SendContact -> {
|
|
commandSender.sendAdmin(myNodeNum) { addContact = action.contact }
|
|
}
|
|
is ServiceAction.GetDeviceMetadata -> {
|
|
commandSender.sendAdmin(action.destNum, wantResponse = true) { getDeviceMetadataRequest = true }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun rememberReaction(action: ServiceAction.Reaction) {
|
|
scope.handledLaunch {
|
|
val reaction =
|
|
ReactionEntity(
|
|
replyId = action.replyId,
|
|
userId = DataPacket.ID_LOCAL,
|
|
emoji = action.emoji,
|
|
timestamp = System.currentTimeMillis(),
|
|
snr = 0f,
|
|
rssi = 0,
|
|
hopsAway = 0,
|
|
)
|
|
packetRepository.get().insertReaction(reaction)
|
|
}
|
|
}
|
|
|
|
fun handleSetOwner(u: org.meshtastic.core.model.MeshUser, myNodeNum: Int) {
|
|
commandSender.sendAdmin(myNodeNum) {
|
|
setOwner = user {
|
|
id = u.id
|
|
longName = u.longName
|
|
shortName = u.shortName
|
|
isLicensed = u.isLicensed
|
|
}
|
|
}
|
|
nodeManager.handleReceivedUser(
|
|
myNodeNum,
|
|
user {
|
|
id = u.id
|
|
longName = u.longName
|
|
shortName = u.shortName
|
|
isLicensed = u.isLicensed
|
|
},
|
|
)
|
|
}
|
|
|
|
fun handleSend(p: DataPacket, myNodeNum: Int) {
|
|
commandSender.sendData(p)
|
|
serviceBroadcasts.broadcastMessageStatus(p)
|
|
dataHandler.rememberDataPacket(p, myNodeNum, false)
|
|
val bytes = p.bytes ?: ByteArray(0)
|
|
analytics.track("data_send", DataPair("num_bytes", bytes.size), DataPair("type", p.dataType))
|
|
}
|
|
|
|
fun handleRequestPosition(destNum: Int, position: Position, myNodeNum: Int) {
|
|
if (destNum != myNodeNum) {
|
|
val provideLocation = meshPrefs.shouldProvideNodeLocation(myNodeNum)
|
|
val currentPosition =
|
|
when {
|
|
provideLocation && position.isValid() -> position
|
|
else ->
|
|
nodeManager.nodeDBbyNodeNum[myNodeNum]?.position?.let { Position(it) }?.takeIf { it.isValid() }
|
|
}
|
|
currentPosition?.let { commandSender.requestPosition(destNum, it) }
|
|
}
|
|
}
|
|
|
|
fun handleRemoveByNodenum(nodeNum: Int, requestId: Int, myNodeNum: Int) {
|
|
nodeManager.removeByNodenum(nodeNum)
|
|
commandSender.sendAdmin(myNodeNum, requestId) { removeByNodenum = nodeNum }
|
|
}
|
|
|
|
fun handleSetRemoteOwner(id: Int, payload: ByteArray, myNodeNum: Int) {
|
|
val u = MeshProtos.User.parseFrom(payload)
|
|
commandSender.sendAdmin(myNodeNum, id) { setOwner = u }
|
|
}
|
|
|
|
fun handleGetRemoteOwner(id: Int, destNum: Int) {
|
|
commandSender.sendAdmin(destNum, id, wantResponse = true) { getOwnerRequest = true }
|
|
}
|
|
|
|
fun handleSetConfig(payload: ByteArray, myNodeNum: Int) {
|
|
val c = ConfigProtos.Config.parseFrom(payload)
|
|
commandSender.sendAdmin(myNodeNum) { setConfig = c }
|
|
}
|
|
|
|
fun handleSetRemoteConfig(id: Int, num: Int, payload: ByteArray) {
|
|
val c = ConfigProtos.Config.parseFrom(payload)
|
|
commandSender.sendAdmin(num, id) { setConfig = c }
|
|
}
|
|
|
|
fun handleGetRemoteConfig(id: Int, destNum: Int, config: Int) {
|
|
commandSender.sendAdmin(destNum, id, wantResponse = true) {
|
|
if (config == AdminProtos.AdminMessage.ConfigType.SESSIONKEY_CONFIG_VALUE) {
|
|
getDeviceMetadataRequest = true
|
|
} else {
|
|
getConfigRequestValue = config
|
|
}
|
|
}
|
|
}
|
|
|
|
fun handleSetModuleConfig(id: Int, num: Int, payload: ByteArray) {
|
|
val c = ModuleConfigProtos.ModuleConfig.parseFrom(payload)
|
|
commandSender.sendAdmin(num, id) { setModuleConfig = c }
|
|
}
|
|
|
|
fun handleGetModuleConfig(id: Int, destNum: Int, config: Int) {
|
|
commandSender.sendAdmin(destNum, id, wantResponse = true) { getModuleConfigRequestValue = config }
|
|
}
|
|
|
|
fun handleSetRingtone(destNum: Int, ringtone: String) {
|
|
commandSender.sendAdmin(destNum) { setRingtoneMessage = ringtone }
|
|
}
|
|
|
|
fun handleGetRingtone(id: Int, destNum: Int) {
|
|
commandSender.sendAdmin(destNum, id, wantResponse = true) { getRingtoneRequest = true }
|
|
}
|
|
|
|
fun handleSetCannedMessages(destNum: Int, messages: String) {
|
|
commandSender.sendAdmin(destNum) { setCannedMessageModuleMessages = messages }
|
|
}
|
|
|
|
fun handleGetCannedMessages(id: Int, destNum: Int) {
|
|
commandSender.sendAdmin(destNum, id, wantResponse = true) { getCannedMessageModuleMessagesRequest = true }
|
|
}
|
|
|
|
fun handleSetChannel(payload: ByteArray?, myNodeNum: Int) {
|
|
if (payload != null) {
|
|
val c = ChannelProtos.Channel.parseFrom(payload)
|
|
commandSender.sendAdmin(myNodeNum) { setChannel = c }
|
|
}
|
|
}
|
|
|
|
fun handleSetRemoteChannel(id: Int, num: Int, payload: ByteArray?) {
|
|
if (payload != null) {
|
|
val c = ChannelProtos.Channel.parseFrom(payload)
|
|
commandSender.sendAdmin(num, id) { setChannel = c }
|
|
}
|
|
}
|
|
|
|
fun handleGetRemoteChannel(id: Int, destNum: Int, index: Int) {
|
|
commandSender.sendAdmin(destNum, id, wantResponse = true) { getChannelRequest = index + 1 }
|
|
}
|
|
|
|
fun handleRequestNeighborInfo(requestId: Int, destNum: Int) {
|
|
commandSender.requestNeighborInfo(requestId, destNum)
|
|
}
|
|
|
|
fun handleBeginEditSettings(myNodeNum: Int) {
|
|
commandSender.sendAdmin(myNodeNum) { beginEditSettings = true }
|
|
}
|
|
|
|
fun handleCommitEditSettings(myNodeNum: Int) {
|
|
commandSender.sendAdmin(myNodeNum) { commitEditSettings = true }
|
|
}
|
|
|
|
fun handleRebootToDfu(myNodeNum: Int) {
|
|
commandSender.sendAdmin(myNodeNum) { enterDfuModeRequest = true }
|
|
}
|
|
|
|
fun handleRequestTelemetry(requestId: Int, destNum: Int, type: Int) {
|
|
commandSender.requestTelemetry(requestId, destNum, type)
|
|
}
|
|
|
|
fun handleRequestShutdown(requestId: Int, destNum: Int) {
|
|
commandSender.sendAdmin(destNum, requestId) { shutdownSeconds = DEFAULT_REBOOT_DELAY }
|
|
}
|
|
|
|
fun handleRequestReboot(requestId: Int, destNum: Int) {
|
|
commandSender.sendAdmin(destNum, requestId) { rebootSeconds = DEFAULT_REBOOT_DELAY }
|
|
}
|
|
|
|
fun handleRequestFactoryReset(requestId: Int, destNum: Int) {
|
|
commandSender.sendAdmin(destNum, requestId) { factoryResetDevice = 1 }
|
|
}
|
|
|
|
fun handleRequestNodedbReset(requestId: Int, destNum: Int, preserveFavorites: Boolean) {
|
|
commandSender.sendAdmin(destNum, requestId) { nodedbReset = preserveFavorites }
|
|
}
|
|
|
|
fun handleGetDeviceConnectionStatus(requestId: Int, destNum: Int) {
|
|
commandSender.sendAdmin(destNum, requestId, wantResponse = true) { getDeviceConnectionStatusRequest = true }
|
|
}
|
|
|
|
fun handleUpdateLastAddress(deviceAddr: String?) {
|
|
val currentAddr = meshPrefs.deviceAddress
|
|
if (deviceAddr != currentAddr) {
|
|
meshPrefs.deviceAddress = deviceAddr
|
|
scope.handledLaunch {
|
|
nodeManager.clear()
|
|
databaseManager.switchActiveDatabase(deviceAddr)
|
|
serviceNotifications.clearNotifications()
|
|
nodeManager.loadCachedNodeDB()
|
|
}
|
|
}
|
|
}
|
|
}
|