2020-02-10 15:31:56 -08:00
package com.geeksville.mesh.service
2020-01-22 21:25:31 -08:00
2020-10-21 17:51:30 +08:00
import android.app.Service
2020-02-25 08:10:23 -08:00
import android.content.Context
import android.content.Intent
2023-06-18 17:33:06 -03:00
import android.content.pm.ServiceInfo
2020-01-22 21:25:31 -08:00
import android.os.IBinder
2020-02-17 18:46:20 -08:00
import android.os.RemoteException
2023-01-03 21:02:31 -03:00
import androidx.core.app.ServiceCompat
2020-04-19 17:25:20 -07:00
import androidx.core.content.edit
2022-09-04 22:52:40 -03:00
import com.geeksville.mesh.analytics.DataPair
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.concurrent.handledLaunch
2020-02-10 17:05:51 -08:00
import com.geeksville.mesh.*
2022-09-12 19:07:30 -03:00
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
2020-01-24 20:35:42 -08:00
import com.geeksville.mesh.MeshProtos.MeshPacket
import com.geeksville.mesh.MeshProtos.ToRadio
2021-06-10 10:58:45 -07:00
import com.geeksville.mesh.android.hasBackgroundPermission
2022-09-13 22:49:38 -03:00
import com.geeksville.mesh.database.MeshLogRepository
2022-09-14 01:54:13 -03:00
import com.geeksville.mesh.database.PacketRepository
2022-09-13 22:49:38 -03:00
import com.geeksville.mesh.database.entity.MeshLog
2022-09-14 01:54:13 -03:00
import com.geeksville.mesh.database.entity.Packet
2021-03-02 15:12:57 +08:00
import com.geeksville.mesh.model.DeviceVersion
2023-05-20 11:42:15 -03:00
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
2022-05-20 09:13:59 -03:00
import com.geeksville.mesh.repository.location.LocationRepository
2022-04-22 10:22:03 -07:00
import com.geeksville.mesh.repository.radio.BluetoothInterface
import com.geeksville.mesh.repository.radio.RadioInterfaceService
2022-04-22 17:39:48 -07:00
import com.geeksville.mesh.repository.radio.RadioServiceConnectionState
2022-09-04 22:52:40 -03:00
import com.geeksville.mesh.util.*
2020-01-24 20:35:42 -08:00
import com.google.protobuf.ByteString
2020-05-11 11:44:24 -07:00
import com.google.protobuf.InvalidProtocolBufferException
2022-02-08 13:50:21 -08:00
import dagger.Lazy
import dagger.hilt.android.AndroidEntryPoint
2023-02-03 19:41:30 -03:00
import java8.util.concurrent.CompletableFuture
2022-12-24 00:20:54 -03:00
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
2022-05-20 09:13:59 -03:00
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
2023-02-18 08:20:36 -03:00
import kotlinx.coroutines.withTimeoutOrNull
2020-04-19 17:25:20 -07:00
import kotlinx.serialization.json.Json
2020-05-31 11:23:25 -07:00
import java.util.*
2023-02-13 18:38:22 -03:00
import java.util.concurrent.ConcurrentLinkedQueue
2023-01-17 18:46:04 -03:00
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
2022-02-08 13:50:21 -08:00
import javax.inject.Inject
2020-05-31 11:23:25 -07:00
import kotlin.math.absoluteValue
2020-01-24 17:05:55 -08:00
2020-01-23 08:09:50 -08:00
/ * *
2020-01-24 17:05:55 -08:00
* Handles all the communication with android apps . Also keeps an internal model
* of the network state .
*
2020-01-23 08:09:50 -08:00
* Note : this service will go away once all clients are unbound from it .
2020-10-01 22:20:19 +02:00
* Warning : do not override toString , it causes infinite recursion on some androids ( because contextWrapper . getResources calls to string
2020-01-23 08:09:50 -08:00
* /
2022-02-08 13:50:21 -08:00
@AndroidEntryPoint
2020-01-22 22:16:30 -08:00
class MeshService : Service ( ) , Logging {
2022-04-22 17:39:48 -07:00
@Inject
lateinit var dispatchers : CoroutineDispatchers
2022-09-14 01:54:13 -03:00
@Inject
lateinit var packetRepository : Lazy < PacketRepository >
2022-02-08 13:50:21 -08:00
@Inject
2022-09-13 22:49:38 -03:00
lateinit var meshLogRepository : Lazy < MeshLogRepository >
2020-01-22 22:16:30 -08:00
2022-04-22 17:39:48 -07:00
@Inject
lateinit var radioInterfaceService : RadioInterfaceService
2022-05-20 09:13:59 -03:00
@Inject
lateinit var locationRepository : LocationRepository
2022-06-11 18:36:57 -03:00
@Inject
2023-05-20 11:42:15 -03:00
lateinit var radioConfigRepository : RadioConfigRepository
2022-09-12 19:07:30 -03:00
2020-02-12 15:47:06 -08:00
companion object : Logging {
2020-02-09 05:52:17 -08:00
/// Intents broadcast by MeshService
2021-01-11 17:15:19 +08:00
2021-03-03 08:14:40 +08:00
/ * @Deprecated ( message = " Does not filter by port number. For legacy reasons only broadcast for UNKNOWN_APP, switch to ACTION_RECEIVED " )
const val ACTION _RECEIVED _DATA = " $prefix .RECEIVED_DATA " * /
2021-01-11 17:15:19 +08:00
2021-03-24 13:48:32 +08:00
private fun actionReceived ( portNum : String ) = " $prefix .RECEIVED. $portNum "
2021-01-11 17:15:19 +08:00
/// generate a RECEIVED action filter string that includes either the portnumber as an int, or preferably a symbolic name from portnums.proto
fun actionReceived ( portNum : Int ) : String {
val portType = Portnums . PortNum . forNumber ( portNum )
val portStr = portType ?. toString ( ) ?: portNum . toString ( )
return actionReceived ( portStr )
}
2020-02-09 05:52:17 -08:00
const val ACTION _NODE _CHANGE = " $prefix .NODE_CHANGE "
2020-02-10 15:31:56 -08:00
const val ACTION _MESH _CONNECTED = " $prefix .MESH_CONNECTED "
2022-11-29 17:45:04 -03:00
const val ACTION _MESSAGE _STATUS = " $prefix .MESSAGE_STATUS "
2020-02-09 05:52:17 -08:00
2021-02-08 11:22:15 +08:00
open class NodeNotFoundException ( reason : String ) : Exception ( reason )
2023-01-12 17:25:28 -03:00
class InvalidNodeIdException ( id : String ) : NodeNotFoundException ( " Invalid NodeId $id " )
2021-02-08 11:22:15 +08:00
class NodeNumNotFoundException ( id : Int ) : NodeNotFoundException ( " NodeNum not found $id " )
class IdNotFoundException ( id : String ) : NodeNotFoundException ( " ID not found $id " )
2021-02-04 23:39:44 +08:00
2022-11-29 17:47:49 -03:00
class NoDeviceConfigException ( message : String = " No radio settings received (is our app too old?) " ) :
RadioNotConnectedException ( message )
2021-02-04 23:39:44 +08:00
/** We treat software update as similar to loss of comms to the regular bluetooth service (so things like sendPosition for background GPS ignores the problem */
2021-03-27 15:50:16 +08:00
class IsUpdatingException :
2021-02-08 11:22:15 +08:00
RadioNotConnectedException ( " Operation prohibited during firmware update " )
2020-05-11 13:12:44 -07:00
2020-06-07 20:17:47 -07:00
/ * *
* Talk to our running service and try to set a new device address . And then immediately
* call start on the service to possibly promote our service to be a foreground service .
* /
fun changeDeviceAddress ( context : Context , service : IMeshService , address : String ? ) {
service . setDeviceAddress ( address )
startService ( context )
}
2020-10-01 22:20:19 +02:00
fun createIntent ( ) = Intent ( ) . setClassName (
" com.geeksville.mesh " ,
" com.geeksville.mesh.service.MeshService "
)
2021-03-19 22:49:51 +08:00
/ * * The minimmum firmware version we know how to talk to . We ' ll still be able to talk to 1.0 firmwares but only well enough to ask them to firmware update
* /
2022-10-11 19:21:03 -03:00
val minDeviceVersion = DeviceVersion ( " 1.3.43 " )
2020-01-25 10:00:57 -08:00
}
2020-04-19 17:25:20 -07:00
enum class ConnectionState {
2020-04-04 15:29:16 -07:00
DISCONNECTED ,
CONNECTED ,
DEVICE _SLEEP // device is in LS sleep state, it will reconnected to us over bluetooth once it has data
}
2021-02-07 17:38:54 -08:00
private var previousSummary : String ? = null
2020-01-26 11:33:51 -08:00
/// A mapping of receiver class name to package name - used for explicit broadcasts
private val clientPackages = mutableMapOf < String , String > ( )
2020-10-01 22:20:19 +02:00
private val serviceNotifications = MeshServiceNotifications ( this )
private val serviceBroadcasts = MeshServiceBroadcasts ( this , clientPackages ) { connectionState }
2020-04-04 14:37:44 -07:00
private val serviceJob = Job ( )
private val serviceScope = CoroutineScope ( Dispatchers . IO + serviceJob )
2020-04-04 15:29:16 -07:00
private var connectionState = ConnectionState . DISCONNECTED
2021-03-03 07:30:05 +08:00
2022-05-20 09:13:59 -03:00
private var locationFlow : Job ? = null
2020-09-23 22:47:45 -04:00
2021-02-08 12:32:23 +08:00
private fun getSenderName ( packet : DataPacket ? ) : String {
2021-02-07 17:38:54 -08:00
val name = nodeDBbyID [ packet ?. from ] ?. user ?. longName
2022-12-28 17:37:25 -03:00
return name ?: getString ( R . string . unknown _username )
2020-02-16 14:22:24 -08:00
}
2020-10-01 22:20:19 +02:00
private val notificationSummary
get ( ) = when ( connectionState ) {
ConnectionState . CONNECTED -> getString ( R . string . connected _count ) . format (
numOnlineNodes ,
numNodes
)
ConnectionState . DISCONNECTED -> getString ( R . string . disconnected )
ConnectionState . DEVICE _SLEEP -> getString ( R . string . device _sleeping )
}
2020-02-16 14:22:24 -08:00
/ * *
2020-02-19 10:53:36 -08:00
* start our location requests ( if they weren ' t already running )
2020-02-16 14:22:24 -08:00
* /
2022-05-20 09:13:59 -03:00
private fun startLocationRequests ( ) {
// If we're already observing updates, don't register again
if ( locationFlow ?. isActive == true ) return
2022-09-05 00:14:08 -03:00
if ( hasBackgroundPermission ( ) ) {
2022-05-20 09:13:59 -03:00
locationFlow = locationRepository . getLocations ( )
. onEach { location ->
sendPosition (
location . latitude ,
location . longitude ,
location . altitude . toInt ( ) ,
2022-10-07 21:38:53 -03:00
( location . time / 1000 ) . toInt ( ) ,
2022-05-20 09:13:59 -03:00
)
2022-12-24 00:20:54 -03:00
}
. launchIn ( serviceScope )
2020-02-19 10:53:36 -08:00
}
2020-02-16 14:22:24 -08:00
}
private fun stopLocationRequests ( ) {
2022-05-20 09:13:59 -03:00
if ( locationFlow ?. isActive == true ) {
2020-02-19 10:53:36 -08:00
debug ( " Stopping location requests " )
2022-05-20 09:13:59 -03:00
locationFlow ?. cancel ( )
2023-01-12 17:25:28 -03:00
locationFlow = null
2020-02-19 10:53:36 -08:00
}
2020-02-16 14:22:24 -08:00
}
2020-02-10 15:31:56 -08:00
2023-01-12 17:25:28 -03:00
/ * * Send a command / packet to our radio . But cope with the possibility that we might start up
2021-03-27 18:15:57 +08:00
before we are fully bound to the RadioInterfaceService
* /
2022-04-22 17:39:48 -07:00
private fun sendToRadio ( p : ToRadio . Builder ) {
2021-05-09 09:02:53 +08:00
val built = p . build ( )
2021-05-10 08:19:27 +08:00
debug ( " Sending to radio ${built.toPIIString()} " )
2021-05-09 09:02:53 +08:00
val b = built . toByteArray ( )
2020-01-27 19:23:34 -08:00
2023-01-12 17:25:28 -03:00
if ( SoftwareUpdateService . isUpdating ) throw IsUpdatingException ( )
2021-01-08 15:19:39 +08:00
2022-04-22 17:39:48 -07:00
radioInterfaceService . sendToRadio ( b )
2023-02-18 08:20:36 -03:00
changeStatus ( p . packet . id , MessageStatus . ENROUTE )
2023-01-12 17:34:17 -03:00
if ( p . packet . hasDecoded ( ) ) {
val packetToSave = MeshLog (
UUID . randomUUID ( ) . toString ( ) ,
" Packet " ,
System . currentTimeMillis ( ) ,
p . packet . toString ( )
)
insertMeshLog ( packetToSave )
}
2020-01-24 20:35:42 -08:00
}
2020-04-22 07:59:07 -07:00
/ * *
* Send a mesh packet to the radio , if the radio is not currently connected this function will throw NotConnectedException
* /
2022-04-22 17:39:48 -07:00
private fun sendToRadio ( packet : MeshPacket ) {
2023-01-17 18:46:04 -03:00
queuedPackets . add ( packet )
startPacketQueue ( )
2020-04-22 07:59:07 -07:00
}
2021-02-07 17:38:54 -08:00
private fun updateMessageNotification ( message : DataPacket ) =
serviceNotifications . updateMessageNotification (
2022-12-10 00:45:50 -03:00
getSenderName ( message ) , message . bytes !! . decodeToString ( )
2021-02-08 12:32:23 +08:00
)
2020-02-28 13:53:16 -08:00
override fun onCreate ( ) {
super . onCreate ( )
2020-02-04 13:24:04 -08:00
2020-02-28 13:53:16 -08:00
info ( " Creating mesh service " )
2020-02-04 13:24:04 -08:00
2020-04-19 17:25:20 -07:00
// Switch to the IO thread
serviceScope . handledLaunch {
loadSettings ( ) // Load our last known node DB
2020-02-04 12:12:29 -08:00
2020-04-19 17:25:20 -07:00
// We in turn need to use the radiointerface service
2022-04-22 17:39:48 -07:00
radioInterfaceService . connect ( )
2020-04-19 17:25:20 -07:00
}
2022-12-24 00:20:54 -03:00
radioInterfaceService . connectionState . onEach ( :: onRadioConnectionState )
. launchIn ( serviceScope )
radioInterfaceService . receivedData . onEach ( :: onReceiveFromRadio )
. launchIn ( serviceScope )
2023-05-20 11:42:15 -03:00
radioConfigRepository . localConfigFlow . onEach { localConfig = it }
2022-12-24 00:20:54 -03:00
. launchIn ( serviceScope )
2023-05-20 11:42:15 -03:00
radioConfigRepository . channelSetFlow . onEach { channelSet = it }
2023-02-20 21:48:55 -03:00
. launchIn ( serviceScope )
2022-05-06 12:17:17 -07:00
// the rest of our init will happen once we are in radioConnection.onServiceConnected
2020-01-24 17:05:55 -08:00
}
2020-06-07 17:51:51 -07:00
/ * *
* If someone binds to us , this will be called after on create
* /
2022-09-16 18:17:19 -03:00
override fun onBind ( intent : Intent ? ) : IBinder {
2020-06-07 17:51:51 -07:00
return binder
}
/ * *
* If someone starts us ( or restarts us ) this will be called after onCreate )
* /
override fun onStartCommand ( intent : Intent ? , flags : Int , startId : Int ) : Int {
2023-01-02 21:12:57 -03:00
val a = radioInterfaceService . getBondedDeviceAddress ( )
val wantForeground = a != null && a != " n "
info ( " Requesting foreground service= $wantForeground " )
// We always start foreground because that's how our service is always started (if we didn't then android would kill us)
// but if we don't really need foreground we immediately stop it.
val notification = serviceNotifications . createServiceStateNotification ( notificationSummary )
2020-06-07 17:51:51 -07:00
2023-06-18 17:33:06 -03:00
if ( android . os . Build . VERSION . SDK _INT >= android . os . Build . VERSION_CODES . Q ) {
startForeground (
serviceNotifications . notifyId ,
notification ,
ServiceInfo . FOREGROUND _SERVICE _TYPE _MANIFEST
)
} else {
startForeground ( serviceNotifications . notifyId , notification )
}
2023-01-02 21:12:57 -03:00
return if ( ! wantForeground ) {
2023-01-03 21:02:31 -03:00
ServiceCompat . stopForeground ( this , ServiceCompat . STOP _FOREGROUND _REMOVE )
2023-01-02 21:12:57 -03:00
START _NOT _STICKY
} else {
START _STICKY
}
2020-06-07 17:51:51 -07:00
}
2020-01-27 16:00:00 -08:00
2020-01-24 17:05:55 -08:00
override fun onDestroy ( ) {
2020-01-25 10:00:57 -08:00
info ( " Destroying mesh service " )
2020-04-20 20:44:21 -07:00
2020-04-19 17:25:20 -07:00
saveSettings ( )
2020-01-27 16:00:00 -08:00
2023-01-03 21:02:31 -03:00
// Make sure we aren't using the notification first
ServiceCompat . stopForeground ( this , ServiceCompat . STOP _FOREGROUND _REMOVE )
2021-02-05 09:31:25 +08:00
serviceNotifications . close ( )
2020-01-24 17:05:55 -08:00
super . onDestroy ( )
2020-04-04 14:37:44 -07:00
serviceJob . cancel ( )
2020-01-24 17:05:55 -08:00
}
2020-01-24 22:22:30 -08:00
///
/// BEGINNING OF MODEL - FIXME, move elsewhere
///
2020-04-19 17:25:20 -07:00
private fun getPrefs ( ) = getSharedPreferences ( " service-prefs " , Context . MODE _PRIVATE )
/// Save information about our mesh to disk, so we will have it when we next start the service (even before we hear from our device)
2023-04-03 18:12:24 -03:00
private fun saveSettings ( ) = synchronized ( nodeDBbyNodeNum ) {
2020-04-19 17:25:20 -07:00
myNodeInfo ?. let { myInfo ->
2020-10-01 22:20:19 +02:00
val settings = MeshServiceSettingsData (
2020-04-19 17:25:20 -07:00
myInfo = myInfo ,
nodeDB = nodeDBbyNodeNum . values . toTypedArray ( ) ,
)
2022-11-15 11:44:47 -03:00
val json = Json { isLenient = true ; allowSpecialFloatingPointValues = true }
2020-10-01 22:20:19 +02:00
val asString = json . encodeToString ( MeshServiceSettingsData . serializer ( ) , settings )
2020-06-09 10:21:54 -07:00
debug ( " Saving settings " )
2022-06-20 22:46:45 -03:00
getPrefs ( ) . edit {
2020-04-19 17:25:20 -07:00
// FIXME, not really ideal to store this bigish blob in preferences
putString ( " json " , asString )
}
}
}
2021-03-15 10:31:44 +08:00
private fun installNewNodeDB ( ni : MyNodeInfo , nodes : Array < NodeInfo > ) {
2020-04-22 18:34:22 -07:00
discardNodeDB ( ) // Get rid of any old state
2021-03-15 10:31:44 +08:00
myNodeInfo = ni
2020-04-22 18:34:22 -07:00
// put our node array into our two different map representations
nodeDBbyNodeNum . putAll ( nodes . map { Pair ( it . num , it ) } )
nodeDBbyID . putAll ( nodes . mapNotNull {
it . user ?. let { user -> // ignore records that don't have a valid user
2022-06-20 22:46:45 -03:00
Pair ( user . id , it )
2020-04-22 18:34:22 -07:00
}
} )
}
2020-04-19 17:25:20 -07:00
private fun loadSettings ( ) {
try {
getPrefs ( ) . getString ( " json " , null ) ?. let { asString ->
2020-04-20 08:27:08 -07:00
2022-11-15 11:44:47 -03:00
val json = Json { isLenient = true ; allowSpecialFloatingPointValues = true }
2020-10-01 22:20:19 +02:00
val settings = json . decodeFromString ( MeshServiceSettingsData . serializer ( ) , asString )
2020-04-22 18:34:22 -07:00
installNewNodeDB ( settings . myInfo , settings . nodeDB )
2020-04-19 17:25:20 -07:00
// Note: we do not haveNodeDB = true because that means we've got a valid db from a real device (rather than this possibly stale hint)
}
} catch ( ex : Exception ) {
errormsg ( " Ignoring error loading saved state for service: ${ex.message} " )
}
}
2020-04-20 07:46:06 -07:00
/ * *
2020-07-01 17:47:53 -07:00
* discard entire node db & message state - used when downloading a new db from the device
2020-04-20 07:46:06 -07:00
* /
2023-04-03 18:12:24 -03:00
private fun discardNodeDB ( ) = synchronized ( nodeDBbyNodeNum ) {
2020-07-04 16:54:48 -07:00
debug ( " Discarding NodeDB " )
2020-04-20 07:46:06 -07:00
myNodeInfo = null
nodeDBbyNodeNum . clear ( )
nodeDBbyID . clear ( )
haveNodeDB = false
}
2020-02-16 14:22:24 -08:00
var myNodeInfo : MyNodeInfo ? = null
2022-09-12 19:07:30 -03:00
private var localConfig : LocalConfig = LocalConfig . getDefaultInstance ( )
2023-02-20 21:48:55 -03:00
private var channelSet : AppOnlyProtos . ChannelSet = AppOnlyProtos . ChannelSet . getDefaultInstance ( )
2021-02-27 14:31:52 +08:00
2020-03-30 17:35:33 -07:00
/// True after we've done our initial node db init
2020-06-12 17:02:21 -07:00
@Volatile
2020-03-30 17:35:33 -07:00
private var haveNodeDB = false
2020-01-24 20:35:42 -08:00
// The database of active nodes, index is the node number
private val nodeDBbyNodeNum = mutableMapOf < Int , NodeInfo > ( )
/// The database of active nodes, index is the node user ID string
2020-01-24 22:22:30 -08:00
/// NOTE: some NodeInfos might be in only nodeDBbyNodeNum (because we don't yet know
/// an ID). But if a NodeInfo is in both maps, it must be one instance shared by
/// both datastructures.
2020-01-24 20:35:42 -08:00
private val nodeDBbyID = mutableMapOf < String , NodeInfo > ( )
2020-01-24 22:22:30 -08:00
///
/// END OF MODEL
///
2020-01-24 20:35:42 -08:00
2023-01-12 17:25:28 -03:00
private val deviceVersion get ( ) = DeviceVersion ( myNodeInfo ?. firmwareVersion ?: " " )
private val appVersion get ( ) = BuildConfig . VERSION _CODE
private val minAppVersion get ( ) = myNodeInfo ?. minAppVersion ?: 0
2020-12-10 09:23:02 +08:00
2020-01-24 22:22:30 -08:00
/// Map a nodenum to a node, or throw an exception if not found
2023-01-12 17:25:28 -03:00
private fun toNodeInfo ( n : Int ) = nodeDBbyNodeNum [ n ] ?: throw NodeNumNotFoundException ( n )
2020-01-24 22:22:30 -08:00
2023-01-12 17:25:28 -03:00
/ * * Map a nodeNum to the nodeId string
2021-02-08 11:22:15 +08:00
If we have a NodeInfo for this ID we prefer to return the string ID inside the user record .
but some nodes might not have a user record at all ( because not yet received ) , in that case , we return
a hex version of the ID just based on the number * /
2023-01-12 17:25:28 -03:00
private fun toNodeID ( n : Int ) : String =
if ( n == DataPacket . NODENUM _BROADCAST ) DataPacket . ID _BROADCAST
else nodeDBbyNodeNum [ n ] ?. user ?. id ?: DataPacket . nodeNumToDefaultId ( n )
2020-01-24 22:22:30 -08:00
/// given a nodenum, return a db entry - creating if necessary
2020-01-25 06:16:10 -08:00
private fun getOrCreateNodeInfo ( n : Int ) =
2022-05-20 09:12:55 -03:00
nodeDBbyNodeNum . getOrPut ( n ) { NodeInfo ( n ) }
2020-01-24 22:22:30 -08:00
2021-02-08 11:22:15 +08:00
private val hexIdRegex = """ \!([0-9A-Fa-f]+) """ . toRegex ( )
2020-01-24 22:22:30 -08:00
/// Map a userid to a node/ node num, or throw an exception if not found
2021-02-08 11:22:15 +08:00
/// We prefer to find nodes based on their assigned IDs, but if no ID has been assigned to a node, we can also find it based on node number
private fun toNodeInfo ( id : String ) : NodeInfo {
// If this is a valid hexaddr will be !null
val hexStr = hexIdRegex . matchEntire ( id ) ?. groups ?. get ( 1 ) ?. value
return nodeDBbyID [ id ] ?: when {
id == DataPacket . ID _LOCAL -> toNodeInfo ( myNodeNum )
hexStr != null -> {
val n = hexStr . toLong ( 16 ) . toInt ( )
nodeDBbyNodeNum [ n ] ?: throw IdNotFoundException ( id )
}
2023-01-12 17:25:28 -03:00
else -> throw InvalidNodeIdException ( id )
2021-02-08 11:22:15 +08:00
}
}
2020-01-26 15:01:59 -08:00
2020-02-25 09:28:47 -08:00
private val numNodes get ( ) = nodeDBbyNodeNum . size
2020-02-19 10:53:36 -08:00
/ * *
* How many nodes are currently online ( including our local node )
* /
2023-04-07 11:28:17 -03:00
private val numOnlineNodes
get ( ) = synchronized ( nodeDBbyNodeNum ) { nodeDBbyNodeNum . values . count { it . isOnline } }
2020-01-24 22:22:30 -08:00
2021-02-08 11:22:15 +08:00
private fun toNodeNum ( id : String ) : Int = when ( id ) {
DataPacket . ID _BROADCAST -> DataPacket . NODENUM _BROADCAST
DataPacket . ID _LOCAL -> myNodeNum
else -> toNodeInfo ( id ) . num
}
2020-01-24 22:22:30 -08:00
/// A helper function that makes it easy to update node info objects
2021-03-27 17:29:46 +08:00
private fun updateNodeInfo (
nodeNum : Int ,
withBroadcast : Boolean = true ,
updateFn : ( NodeInfo ) -> Unit
) {
2020-01-24 22:22:30 -08:00
val info = getOrCreateNodeInfo ( nodeNum )
2021-03-27 16:14:57 +08:00
updateFn ( info )
2020-02-04 12:12:29 -08:00
// This might have been the first time we know an ID for this node, so also update the by ID map
2020-02-14 04:41:20 -08:00
val userId = info . user ?. id . orEmpty ( )
if ( userId . isNotEmpty ( ) )
2020-02-04 12:12:29 -08:00
nodeDBbyID [ userId ] = info
2020-02-09 05:52:17 -08:00
2020-02-09 10:18:26 -08:00
// parcelable is busted
2021-03-27 17:29:46 +08:00
if ( withBroadcast )
2021-03-27 16:14:57 +08:00
serviceBroadcasts . broadcastNodeChange ( info )
2020-01-24 22:22:30 -08:00
}
2020-02-17 13:14:53 -08:00
/// My node num
2020-04-22 18:34:22 -07:00
private val myNodeNum
get ( ) = myNodeInfo ?. myNodeNum
?: throw RadioNotConnectedException ( " We don't yet have our myNodeInfo " )
2020-02-17 13:14:53 -08:00
/// My node ID string
private val myNodeID get ( ) = toNodeID ( myNodeNum )
2022-10-10 18:09:20 -03:00
/// Admin channel index
2023-05-21 19:46:40 -03:00
private val adminChannelIndex : Int
2023-05-24 06:17:32 -03:00
get ( ) = channelSet . settingsList . indexOfFirst { it . name . lowercase ( ) == " admin " }
. coerceAtLeast ( 0 )
2022-10-10 18:09:20 -03:00
2020-01-24 22:22:30 -08:00
/// Generate a new mesh packet builder with our node as the sender, and the specified node num
private fun newMeshPacketTo ( idNum : Int ) = MeshPacket . newBuilder ( ) . apply {
2020-02-16 14:22:24 -08:00
if ( myNodeInfo == null )
2020-01-25 12:24:53 -08:00
throw RadioNotConnectedException ( )
2022-05-30 17:43:05 -03:00
from = 0 // don't add myNodeNum
2020-06-07 22:15:47 -07:00
2021-02-27 10:18:00 +08:00
to = idNum
2020-01-24 20:35:42 -08:00
}
2020-02-17 15:39:49 -08:00
/ * *
* Generate a new mesh packet builder with our node as the sender , and the specified recipient
*
* If id is null we assume a broadcast message
* /
2023-04-22 12:06:25 -03:00
private fun newMeshPacketTo ( id : String ) = newMeshPacketTo ( toNodeNum ( id ) )
2020-01-24 22:22:30 -08:00
2020-02-17 15:39:49 -08:00
/ * *
* Helper to make it easy to build a subpacket in the proper protobufs
* /
2022-05-20 09:12:55 -03:00
private fun MeshPacket . Builder . buildMeshPacket (
2020-05-21 17:25:04 -07:00
wantAck : Boolean = false ,
2021-03-02 22:12:42 +08:00
id : Int = generatePacketId ( ) , // always assign a packet ID if we didn't already have one
2023-04-25 19:18:03 -03:00
hopLimit : Int = localConfig . lora . hopLimit ,
2022-09-15 22:24:04 -03:00
channel : Int = 0 ,
2021-03-02 16:27:43 +08:00
priority : MeshPacket . Priority = MeshPacket . Priority . UNSET ,
2021-02-27 10:18:00 +08:00
initFn : MeshProtos . Data . Builder . ( ) -> Unit
2021-03-02 16:27:43 +08:00
) : MeshPacket {
2020-05-21 17:25:04 -07:00
this . wantAck = wantAck
2020-05-30 17:28:00 -07:00
this . id = id
2021-02-27 22:28:59 -07:00
this . hopLimit = hopLimit
2022-09-15 22:24:04 -03:00
this . channel = channel
2021-03-02 16:27:43 +08:00
this . priority = priority
2021-02-27 10:18:00 +08:00
decoded = MeshProtos . Data . newBuilder ( ) . also {
2020-02-02 18:38:01 -08:00
initFn ( it )
2020-01-25 10:00:57 -08:00
} . build ( )
2021-03-02 16:27:43 +08:00
return build ( )
}
/ * *
* Helper to make it easy to build a subpacket in the proper protobufs
* /
2022-05-20 09:12:55 -03:00
private fun MeshPacket . Builder . buildAdminPacket (
2023-04-22 12:06:25 -03:00
id : Int = generatePacketId ( ) , // always assign a packet ID if we didn't already have one
2021-03-03 07:30:05 +08:00
wantResponse : Boolean = false ,
2021-03-02 16:27:43 +08:00
initFn : AdminProtos . AdminMessage . Builder . ( ) -> Unit
) : MeshPacket = buildMeshPacket (
2023-04-22 12:06:25 -03:00
id = id ,
2021-03-02 16:27:43 +08:00
wantAck = true ,
2022-10-10 18:09:20 -03:00
channel = adminChannelIndex ,
2021-03-02 16:27:43 +08:00
priority = MeshPacket . Priority . RELIABLE
)
{
2021-03-03 07:30:05 +08:00
this . wantResponse = wantResponse
2021-03-02 16:27:43 +08:00
portnumValue = Portnums . PortNum . ADMIN _APP _VALUE
payload = AdminProtos . AdminMessage . newBuilder ( ) . also {
initFn ( it )
} . build ( ) . toByteString ( )
}
2020-04-19 11:47:34 -07:00
/// Generate a DataPacket from a MeshPacket, or null if we didn't have enough data to do so
private fun toDataPacket ( packet : MeshPacket ) : DataPacket ? {
2021-02-27 10:18:00 +08:00
return if ( ! packet . hasDecoded ( ) ) {
2020-04-19 11:47:34 -07:00
// We never convert packets that are not DataPackets
null
} else {
2021-02-27 10:18:00 +08:00
val data = packet . decoded
2020-04-19 11:47:34 -07:00
val bytes = data . payload . toByteArray ( )
val fromId = toNodeID ( packet . from )
2022-10-07 23:26:00 -03:00
val delayedBroadcast = packet . delayed == MeshPacket . Delayed . DELAYED _BROADCAST
2022-09-03 07:38:36 -03:00
val toId = if ( delayedBroadcast ) DataPacket . ID _BROADCAST else toNodeID ( packet . to )
2021-02-27 22:28:59 -07:00
val hopLimit = packet . hopLimit
2020-04-19 11:47:34 -07:00
// If the rxTime was not set by the device (because device software was old), guess at a time
2021-03-02 14:16:57 +08:00
val rxTime = if ( packet . rxTime != 0 ) packet . rxTime else currentSecond ( )
2020-04-19 11:47:34 -07:00
2023-01-12 17:25:28 -03:00
DataPacket (
from = fromId ,
to = toId ,
time = rxTime * 1000L ,
id = packet . id ,
dataType = data . portnumValue ,
bytes = bytes ,
hopLimit = hopLimit ,
channel = packet . channel ,
)
2020-04-19 11:47:34 -07:00
}
}
2020-05-30 15:48:50 -07:00
private fun toMeshPacket ( p : DataPacket ) : MeshPacket {
2021-03-02 16:27:43 +08:00
return newMeshPacketTo ( p . to !! ) . buildMeshPacket (
id = p . id ,
wantAck = true ,
2022-09-15 22:24:04 -03:00
hopLimit = p . hopLimit ,
channel = p . channel ,
2021-03-02 16:27:43 +08:00
) {
2021-02-27 10:18:00 +08:00
portnumValue = p . dataType
payload = ByteString . copyFrom ( p . bytes )
2020-05-30 15:48:50 -07:00
}
}
2020-04-19 11:47:34 -07:00
private fun rememberDataPacket ( dataPacket : DataPacket ) {
2023-01-27 16:13:49 -03:00
val fromLocal = dataPacket . from == DataPacket . ID _LOCAL
val toBroadcast = dataPacket . to == DataPacket . ID _BROADCAST
val contactId = if ( fromLocal || toBroadcast ) dataPacket . to else dataPacket . from
// contactKey: unique contact key filter (channel)+(nodeId)
val contactKey = " ${dataPacket.channel} $contactId "
val packetToSave = Packet (
0L , // autoGenerated
dataPacket . dataType ,
contactKey ,
System . currentTimeMillis ( ) ,
dataPacket
)
insertPacket ( packetToSave )
2020-04-19 11:47:34 -07:00
}
2020-01-25 06:16:10 -08:00
/// Update our model and resend as needed for a MeshPacket we just received from the radio
2020-04-19 11:47:34 -07:00
private fun handleReceivedData ( packet : MeshPacket ) {
2020-05-31 11:23:25 -07:00
myNodeInfo ?. let { myInfo ->
2021-02-27 10:18:00 +08:00
val data = packet . decoded
2020-05-31 11:23:25 -07:00
val bytes = data . payload . toByteArray ( )
val fromId = toNodeID ( packet . from )
val dataPacket = toDataPacket ( packet )
2020-01-25 06:16:10 -08:00
2020-05-31 11:23:25 -07:00
if ( dataPacket != null ) {
2020-01-25 06:16:10 -08:00
2021-03-03 07:30:05 +08:00
// We ignore most messages that we sent
val fromUs = myInfo . myNodeNum == packet . from
2020-02-09 05:52:17 -08:00
2021-03-03 07:30:05 +08:00
debug ( " Received data from $fromId , portnum= ${data.portnum} ${bytes.size} bytes " )
2020-02-28 14:07:04 -08:00
2021-03-03 07:30:05 +08:00
dataPacket . status = MessageStatus . RECEIVED
2021-02-27 10:18:00 +08:00
2021-03-03 07:30:05 +08:00
// if (p.hasUser()) handleReceivedUser(fromNum, p.user)
2021-03-02 22:12:42 +08:00
2021-03-03 07:30:05 +08:00
/// We tell other apps about most message types, but some may have sensitve data, so that is not shared'
var shouldBroadcast = ! fromUs
when ( data . portnumValue ) {
Portnums . PortNum . TEXT _MESSAGE _APP _VALUE ->
if ( ! fromUs ) {
2020-05-31 11:23:25 -07:00
debug ( " Received CLEAR_TEXT from $fromId " )
2023-01-27 16:13:49 -03:00
rememberDataPacket ( dataPacket )
2021-02-07 17:38:54 -08:00
updateMessageNotification ( dataPacket )
2020-05-31 11:23:25 -07:00
}
2020-01-25 06:16:10 -08:00
2023-01-27 16:13:49 -03:00
Portnums . PortNum . WAYPOINT _APP _VALUE -> {
val u = MeshProtos . Waypoint . parseFrom ( data . payload )
// Validate locked Waypoints from the original sender
if ( u . lockedTo != 0 && u . lockedTo != packet . from ) return
rememberDataPacket ( dataPacket )
}
2021-03-03 07:30:05 +08:00
// Handle new style position info
Portnums . PortNum . POSITION _APP _VALUE -> {
2023-05-10 22:17:09 -03:00
if ( data . wantResponse ) return // ignore data from position requests
2021-03-27 15:50:16 +08:00
var u = MeshProtos . Position . parseFrom ( data . payload )
// position updates from mesh usually don't include times. So promote rx time
2021-03-27 17:29:46 +08:00
if ( u . time == 0 && packet . rxTime != 0 )
2021-03-27 15:50:16 +08:00
u = u . toBuilder ( ) . setTime ( packet . rxTime ) . build ( )
2021-05-10 08:19:27 +08:00
// PII
// debug("position_app ${packet.from} ${u.toOneLineString()}")
2021-03-03 07:30:05 +08:00
handleReceivedPosition ( packet . from , u , dataPacket . time )
}
2020-12-07 19:50:06 +08:00
2021-03-03 07:30:05 +08:00
// Handle new style user info
Portnums . PortNum . NODEINFO _APP _VALUE ->
if ( ! fromUs ) {
2020-12-07 19:50:06 +08:00
val u = MeshProtos . User . parseFrom ( data . payload )
handleReceivedUser ( packet . from , u )
}
2021-02-27 10:18:00 +08:00
2022-03-28 15:50:33 -03:00
// Handle new telemetry info
Portnums . PortNum . TELEMETRY _APP _VALUE -> {
var u = TelemetryProtos . Telemetry . parseFrom ( data . payload )
if ( u . time == 0 && packet . rxTime != 0 )
u = u . toBuilder ( ) . setTime ( packet . rxTime ) . build ( )
handleReceivedTelemetry ( packet . from , u , dataPacket . time )
2022-06-11 18:36:57 -03:00
}
2022-03-28 15:50:33 -03:00
2021-03-03 07:30:05 +08:00
// Handle new style routing info
2021-03-03 07:49:23 +08:00
Portnums . PortNum . ROUTING _APP _VALUE -> {
2021-03-14 11:42:04 +08:00
shouldBroadcast =
true // We always send acks to other apps, because they might care about the messages they sent
2021-03-03 07:49:23 +08:00
val u = MeshProtos . Routing . parseFrom ( data . payload )
2023-01-02 22:23:23 -03:00
val isAck = u . errorReasonValue == MeshProtos . Routing . Error . NONE _VALUE
handleAckNak ( isAck , fromId , data . requestId )
2023-01-17 18:46:04 -03:00
queueResponse . remove ( data . requestId ) ?. complete ( true )
2021-03-03 07:49:23 +08:00
}
2021-03-02 22:12:42 +08:00
2021-03-03 07:30:05 +08:00
Portnums . PortNum . ADMIN _APP _VALUE -> {
val u = AdminProtos . AdminMessage . parseFrom ( data . payload )
handleReceivedAdmin ( packet . from , u )
shouldBroadcast = false
2020-12-07 20:33:29 +08:00
}
2020-03-04 11:16:43 -08:00
2021-03-03 07:30:05 +08:00
else ->
debug ( " No custom processing needed for ${data.portnumValue} " )
}
// We always tell other apps when new data packets arrive
if ( shouldBroadcast )
serviceBroadcasts . broadcastReceivedData ( dataPacket )
2020-04-22 07:59:07 -07:00
2021-03-03 07:30:05 +08:00
GeeksvilleApplication . analytics . track (
" num_data_receive " ,
DataPair ( 1 )
)
2020-05-31 11:23:25 -07:00
2021-03-03 07:30:05 +08:00
GeeksvilleApplication . analytics . track (
" data_receive " ,
DataPair ( " num_bytes " , bytes . size ) ,
DataPair ( " type " , data . portnumValue )
)
2020-05-31 11:23:25 -07:00
}
2020-04-19 11:47:34 -07:00
}
2020-01-25 06:16:10 -08:00
}
2021-03-02 22:12:42 +08:00
private fun handleReceivedAdmin ( fromNodeNum : Int , a : AdminProtos . AdminMessage ) {
// For the time being we only care about admin messages from our local node
if ( fromNodeNum == myNodeNum ) {
2022-09-18 18:35:13 -03:00
when ( a . payloadVariantCase ) {
AdminProtos . AdminMessage . PayloadVariantCase . GET _CONFIG _RESPONSE -> {
2022-05-30 17:43:05 -03:00
val response = a . getConfigResponse
debug ( " Admin: received config ${response.payloadVariantCase} " )
2022-06-10 21:55:26 -03:00
setLocalConfig ( response )
2021-03-02 22:12:42 +08:00
}
2022-09-18 18:35:13 -03:00
AdminProtos . AdminMessage . PayloadVariantCase . GET _CHANNEL _RESPONSE -> {
2021-03-02 22:12:42 +08:00
val mi = myNodeInfo
if ( mi != null ) {
val ch = a . getChannelResponse
2021-03-04 09:08:29 +08:00
debug ( " Admin: Received channel ${ch.index} " )
2022-07-29 19:46:04 -03:00
2021-03-02 22:12:42 +08:00
if ( ch . index + 1 < mi . maxChannels ) {
2022-10-16 19:19:03 -03:00
handleChannel ( ch )
2021-03-02 22:12:42 +08:00
}
}
}
else ->
2022-09-18 18:35:13 -03:00
warn ( " No special processing needed for ${a.payloadVariantCase} " )
2021-03-02 22:12:42 +08:00
}
}
}
2020-01-25 10:00:57 -08:00
/// Update our DB of users based on someone sending out a User subpacket
private fun handleReceivedUser ( fromNum : Int , p : MeshProtos . User ) {
updateNodeInfo ( fromNum ) {
2020-02-25 10:30:10 -08:00
val oldId = it . user ?. id . orEmpty ( )
2020-02-10 15:31:56 -08:00
it . user = MeshUser (
2022-05-20 09:12:55 -03:00
p . id . ifEmpty { oldId } , // If the new update doesn't contain an ID keep our old value
2020-02-10 15:31:56 -08:00
p . longName ,
2021-03-14 11:42:04 +08:00
p . shortName ,
2022-11-08 23:11:18 -03:00
p . hwModel ,
p . isLicensed
2020-02-10 15:31:56 -08:00
)
2020-01-25 10:00:57 -08:00
}
}
2021-03-02 14:16:57 +08:00
/ * * Update our DB of users based on someone sending out a Position subpacket
* @param defaultTime in msecs since 1970
2021-03-02 14:38:55 +08:00
* /
2020-08-18 11:25:16 -07:00
private fun handleReceivedPosition (
fromNum : Int ,
p : MeshProtos . Position ,
2021-03-02 14:16:57 +08:00
defaultTime : Long = System . currentTimeMillis ( )
2020-08-18 11:25:16 -07:00
) {
2023-05-10 22:17:09 -03:00
// Nodes periodically send out position updates, but those updates might not contain a lat & lon (because no GPS lock)
// We like to look at the local node to see if it has been sending out valid lat/lon, so for the LOCAL node (only)
2021-03-27 13:25:01 +08:00
// we don't record these nop position updates
2023-05-10 22:17:09 -03:00
if ( myNodeNum == fromNum && p . latitudeI == 0 && p . longitudeI == 0 )
debug ( " Ignoring nop position update for the local node " )
2021-03-27 13:25:01 +08:00
else
updateNodeInfo ( fromNum ) {
2021-05-10 08:19:27 +08:00
debug ( " update position: ${it.user?.longName?.toPIIString()} with ${p.toPIIString()} " )
2021-03-27 13:25:01 +08:00
it . position = Position ( p , ( defaultTime / 1000L ) . toInt ( ) )
}
2020-02-16 14:22:24 -08:00
}
2022-04-04 19:10:15 -03:00
/// Update our DB of users based on someone sending out a Telemetry subpacket
2022-03-28 15:50:33 -03:00
private fun handleReceivedTelemetry (
fromNum : Int ,
2022-09-08 19:09:36 -03:00
t : TelemetryProtos . Telemetry ,
2022-03-28 15:50:33 -03:00
defaultTime : Long = System . currentTimeMillis ( )
) {
updateNodeInfo ( fromNum ) {
2022-11-06 08:39:05 -03:00
if ( t . hasDeviceMetrics ( ) ) it . deviceMetrics = DeviceMetrics (
t . deviceMetrics , if ( t . time != 0 ) t . time else ( defaultTime / 1000L ) . toInt ( )
2022-09-08 19:09:36 -03:00
)
2022-11-06 08:39:05 -03:00
if ( t . hasEnvironmentMetrics ( ) ) it . environmentMetrics = EnvironmentMetrics (
t . environmentMetrics , if ( t . time != 0 ) t . time else ( defaultTime / 1000L ) . toInt ( )
2022-04-04 19:10:15 -03:00
)
2022-03-28 15:50:33 -03:00
}
}
2020-01-24 22:22:30 -08:00
/// Update our model and resend as needed for a MeshPacket we just received from the radio
private fun handleReceivedMeshPacket ( packet : MeshPacket ) {
2020-03-30 17:35:33 -07:00
if ( haveNodeDB ) {
processReceivedMeshPacket ( packet )
onNodeDBChanged ( )
} else {
2022-06-20 22:46:45 -03:00
warn ( " Ignoring early received packet: ${packet.toOneLineString()} " )
2021-06-11 09:45:04 -07:00
//earlyReceivedPackets.add(packet)
//logAssert(earlyReceivedPackets.size < 128) // The max should normally be about 32, but if the device is messed up it might try to send forever
2020-03-30 17:35:33 -07:00
}
}
2023-02-13 18:38:22 -03:00
private val queuedPackets = ConcurrentLinkedQueue < MeshPacket > ( )
2023-01-17 18:46:04 -03:00
private val queueResponse = mutableMapOf < Int , CompletableFuture < Boolean > > ( )
private var queueJob : Job ? = null
private fun sendPacket ( packet : MeshPacket ) : CompletableFuture < Boolean > {
// send the packet to the radio and return a CompletableFuture that will be completed with the result
val future = CompletableFuture < Boolean > ( )
queueResponse [ packet . id ] = future
try {
2023-02-18 08:20:36 -03:00
if ( connectionState != ConnectionState . CONNECTED ) throw RadioNotConnectedException ( )
2023-01-17 18:46:04 -03:00
sendToRadio ( ToRadio . newBuilder ( ) . apply {
this . packet = packet
} )
} catch ( ex : Exception ) {
errormsg ( " sendToRadio error: " , ex )
future . complete ( false )
}
return future
}
private fun startPacketQueue ( ) {
if ( queueJob ?. isActive == true ) return
queueJob = serviceScope . handledLaunch {
debug ( " packet queueJob started " )
while ( connectionState == ConnectionState . CONNECTED ) {
2023-02-13 18:38:22 -03:00
var retryCount = 0
2023-01-17 18:46:04 -03:00
// take the first packet from the queue head
val packet = queuedPackets . poll ( ) ?: break
2023-02-13 18:38:22 -03:00
while ( retryCount < 3 ) try {
// send packet to the radio and wait for response
val response = sendPacket ( packet )
debug ( " queueJob packet id= ${packet.id.toUInt()} waiting (retry $retryCount ) " )
2023-01-17 18:46:04 -03:00
@Suppress ( " BlockingMethodInNonBlockingContext " )
val success = response . get ( 45 , TimeUnit . SECONDS )
debug ( " queueJob packet id= ${packet.id.toUInt()} success $success " )
2023-02-13 18:38:22 -03:00
if ( success ) break
retryCount ++ // if send operation fails, retry
2023-01-17 18:46:04 -03:00
} catch ( e : TimeoutException ) {
debug ( " queueJob timeout waiting packet id= ${packet.id.toUInt()} " )
2023-02-13 18:38:22 -03:00
retryCount ++ // if send operation fails, retry
}
if ( retryCount >= 3 ) {
debug ( " queueJob packet id= ${packet.id.toUInt()} failed " )
2023-01-17 18:46:04 -03:00
}
}
}
}
private fun stopPacketQueue ( ) {
if ( queueJob ?. isActive == true ) {
debug ( " Stopping packet queueJob " )
queueJob ?. cancel ( )
queueJob = null
queuedPackets . clear ( )
2023-02-18 08:20:36 -03:00
queueResponse . entries . lastOrNull { ! it . value . isDone } ?. value ?. complete ( false )
2023-01-17 18:46:04 -03:00
queueResponse . clear ( )
}
}
2020-06-11 14:03:10 -07:00
private fun sendNow ( p : DataPacket ) {
val packet = toMeshPacket ( p )
p . time = System . currentTimeMillis ( ) // update time to the actual time we started sending
2021-05-10 08:19:27 +08:00
// debug("Sending to radio: ${packet.toPIIString()}")
2020-06-11 14:03:10 -07:00
sendToRadio ( packet )
}
2023-02-18 08:20:36 -03:00
private fun processQueuedPackets ( ) = serviceScope . handledLaunch {
packetRepository . get ( ) . getQueuedPackets ( ) ?. forEach { p ->
2023-06-20 08:22:10 -03:00
// check for duplicate packet IDs before sending (so ACK/NAK updates can work)
if ( getDataPacketById ( p . id ) ?. time != p . time ) {
val newId = generatePacketId ( )
debug ( " Replaced duplicate packet ID in queue: ${p.id} , with: $newId " )
packetRepository . get ( ) . updateMessageId ( p , newId )
p . id = newId
}
2023-02-18 08:20:36 -03:00
try {
sendNow ( p )
} catch ( ex : Exception ) {
errormsg ( " Error sending queued message: " , ex )
2023-01-12 17:47:59 -03:00
}
2020-05-30 15:48:50 -07:00
}
2021-12-25 19:30:45 -03:00
}
2020-03-30 17:35:33 -07:00
2023-02-18 08:20:36 -03:00
private suspend fun getDataPacketById ( packetId : Int ) : DataPacket ? = withTimeoutOrNull ( 1000 ) {
var dataPacket : DataPacket ? = null
while ( dataPacket == null ) {
dataPacket = packetRepository . get ( ) . getDataPacketById ( packetId )
if ( dataPacket == null ) delay ( 100 )
}
dataPacket
}
/ * *
* Change the status on a DataPacket and update watchers
* /
private fun changeStatus ( packetId : Int , m : MessageStatus ) = serviceScope . handledLaunch {
if ( packetId != 0 ) getDataPacketById ( packetId ) ?. let { p ->
if ( p . status == m ) return @handledLaunch
packetRepository . get ( ) . updateMessageStatus ( p , m )
serviceBroadcasts . broadcastMessageStatus ( packetId , m )
}
}
2020-05-30 19:58:36 -07:00
/ * *
* Handle an ack / nak packet by updating sent message status
* /
2023-01-20 19:19:13 -03:00
private fun handleAckNak ( isAck : Boolean , fromId : String , requestId : Int ) {
2023-01-02 22:23:23 -03:00
serviceScope . handledLaunch {
2023-02-18 08:20:36 -03:00
val p = getDataPacketById ( requestId )
2023-01-20 19:19:13 -03:00
// distinguish real ACKs coming from the intended receiver
val m = if ( isAck && fromId == p ?. to ) MessageStatus . RECEIVED
else if ( isAck ) MessageStatus . DELIVERED else MessageStatus . ERROR
if ( p != null && p . status != MessageStatus . RECEIVED )
packetRepository . get ( ) . updateMessageStatus ( p , m )
serviceBroadcasts . broadcastMessageStatus ( requestId , m )
2020-05-30 19:58:36 -07:00
}
}
2020-03-30 17:35:33 -07:00
/// Update our model and resend as needed for a MeshPacket we just received from the radio
private fun processReceivedMeshPacket ( packet : MeshPacket ) {
2020-01-24 22:22:30 -08:00
val fromNum = packet . from
// FIXME, perhaps we could learn our node ID by looking at any to packets the radio
// decided to pass through to us (except for broadcast packets)
2020-02-29 13:42:15 -08:00
//val toNum = packet.to
2020-01-24 22:22:30 -08:00
2020-06-13 16:02:57 -07:00
// debug("Recieved: $packet")
2021-02-27 10:18:00 +08:00
if ( packet . hasDecoded ( ) ) {
2022-09-13 22:49:38 -03:00
val packetToSave = MeshLog (
2021-02-27 10:18:00 +08:00
UUID . randomUUID ( ) . toString ( ) ,
2022-07-29 19:46:04 -03:00
" Packet " ,
2021-02-27 10:18:00 +08:00
System . currentTimeMillis ( ) ,
packet . toString ( )
)
2022-09-13 22:49:38 -03:00
insertMeshLog ( packetToSave )
2020-04-19 11:47:34 -07:00
2021-02-27 10:18:00 +08:00
// Update last seen for the node that sent the packet, but also for _our node_ because anytime a packet passes
// through our node on the way to the phone that means that local node is also alive in the mesh
2020-02-19 11:35:16 -08:00
2021-03-27 16:14:57 +08:00
val isOtherNode = myNodeNum != fromNum
updateNodeInfo ( myNodeNum , withBroadcast = isOtherNode ) {
2021-03-27 10:53:39 +08:00
it . lastHeard = currentSecond ( )
2021-02-27 10:18:00 +08:00
}
2021-03-27 16:14:57 +08:00
// Do not generate redundant broadcasts of node change for this bookkeeping updateNodeInfo call
// because apps really only care about important updates of node state - which handledReceivedData will give them
updateNodeInfo ( fromNum , withBroadcast = false ) {
// If the rxTime was not set by the device (because device software was old), guess at a time
val rxTime = if ( packet . rxTime != 0 ) packet . rxTime else currentSecond ( )
2020-07-23 23:12:01 -04:00
// Update our last seen based on any valid timestamps. If the device didn't provide a timestamp make one
updateNodeInfoTime ( it , rxTime )
2021-03-19 14:33:55 -07:00
it . snr = packet . rxSnr
2021-03-22 21:10:58 -07:00
it . rssi = packet . rxRssi
2020-07-23 23:12:01 -04:00
}
2020-02-16 14:22:24 -08:00
2020-04-19 11:47:34 -07:00
handleReceivedData ( packet )
2021-02-27 10:18:00 +08:00
}
2020-01-24 22:22:30 -08:00
}
2020-01-24 20:35:42 -08:00
2022-09-14 01:54:13 -03:00
private fun insertPacket ( packet : Packet ) {
serviceScope . handledLaunch {
packetRepository . get ( ) . insert ( packet )
}
}
2022-09-13 22:49:38 -03:00
private fun insertMeshLog ( packetToSave : MeshLog ) {
2020-09-23 22:47:45 -04:00
serviceScope . handledLaunch {
2021-05-10 08:19:27 +08:00
// Do not log, because might contain PII
// info("insert: ${packetToSave.message_type} = ${packetToSave.raw_message.toOneLineString()}")
2022-09-13 22:49:38 -03:00
meshLogRepository . get ( ) . insert ( packetToSave )
2020-09-23 22:47:45 -04:00
}
}
2022-06-20 22:46:45 -03:00
private fun setLocalConfig ( config : ConfigProtos . Config ) {
2022-06-11 18:36:57 -03:00
serviceScope . handledLaunch {
2023-05-20 11:42:15 -03:00
radioConfigRepository . setLocalConfig ( config )
2022-06-11 18:36:57 -03:00
}
}
2022-11-22 22:01:37 -03:00
private fun setLocalModuleConfig ( config : ModuleConfigProtos . ModuleConfig ) {
serviceScope . handledLaunch {
2023-05-20 11:42:15 -03:00
radioConfigRepository . setLocalModuleConfig ( config )
2022-11-22 22:01:37 -03:00
}
}
2022-06-20 22:46:45 -03:00
private fun clearLocalConfig ( ) {
serviceScope . handledLaunch {
2023-05-20 11:42:15 -03:00
radioConfigRepository . clearLocalConfig ( )
radioConfigRepository . clearLocalModuleConfig ( )
2022-06-20 22:46:45 -03:00
}
}
2023-05-06 08:08:17 -03:00
private fun updateChannelSettings ( ch : ChannelProtos . Channel ) = serviceScope . handledLaunch {
2023-05-21 19:46:40 -03:00
radioConfigRepository . updateChannelSettings ( ch )
2022-09-12 19:07:30 -03:00
}
2020-02-19 11:35:16 -08:00
private fun currentSecond ( ) = ( System . currentTimeMillis ( ) / 1000 ) . toInt ( )
2020-04-04 15:29:16 -07:00
2020-02-19 10:53:36 -08:00
/// If we just changed our nodedb, we might want to do somethings
private fun onNodeDBChanged ( ) {
2021-02-07 17:38:54 -08:00
maybeUpdateServiceStatusNotification ( )
2021-03-17 21:00:01 -07:00
}
2020-02-19 10:53:36 -08:00
2020-04-22 18:34:22 -07:00
/ * *
* Send in analytics about mesh connection
* /
private fun reportConnection ( ) {
val radioModel = DataPair ( " radio_model " , myNodeInfo ?. model ?: " unknown " )
GeeksvilleApplication . analytics . track (
" mesh_connect " ,
DataPair ( " num_nodes " , numNodes ) ,
DataPair ( " num_online " , numOnlineNodes ) ,
radioModel
)
// Once someone connects to hardware start tracking the approximate number of nodes in their mesh
// this allows us to collect stats on what typical mesh size is and to tell difference between users who just
// downloaded the app, vs has connected it to some hardware.
GeeksvilleApplication . analytics . setUserInfo (
DataPair ( " num_nodes " , numNodes ) ,
radioModel
)
}
2020-04-04 15:29:16 -07:00
private var sleepTimeout : Job ? = null
2020-04-21 14:46:52 -07:00
/// msecs since 1970 we started this connection
private var connectTimeMsec = 0L
2020-02-04 12:12:29 -08:00
/// Called when we gain/lose connection to our radio
2020-04-04 15:29:16 -07:00
private fun onConnectionChanged ( c : ConnectionState ) {
debug ( " onConnectionChanged= $c " )
/// Perform all the steps needed once we start waiting for device sleep to complete
fun startDeviceSleep ( ) {
2020-07-01 17:47:53 -07:00
// Just in case the user uncleanly reboots the phone, save now (we normally save in onDestroy)
saveSettings ( )
2023-01-17 18:46:04 -03:00
stopPacketQueue ( )
2020-04-04 15:29:16 -07:00
stopLocationRequests ( )
2020-04-21 14:46:52 -07:00
if ( connectTimeMsec != 0L ) {
val now = System . currentTimeMillis ( )
connectTimeMsec = 0L
GeeksvilleApplication . analytics . track (
" connected_seconds " ,
DataPair ( ( now - connectTimeMsec ) / 1000.0 )
)
}
2020-04-04 15:29:16 -07:00
// Have our timeout fire in the approprate number of seconds
sleepTimeout = serviceScope . handledLaunch {
try {
// If we have a valid timeout, wait that long (+30 seconds) otherwise, just wait 30 seconds
2022-05-30 17:43:05 -03:00
val timeout = ( localConfig . power ?. lsSecs ?: 0 ) + 30
2020-04-04 15:29:16 -07:00
debug ( " Waiting for sleeping device, timeout= $timeout secs " )
delay ( timeout * 1000L )
warn ( " Device timeout out, setting disconnected " )
onConnectionChanged ( ConnectionState . DISCONNECTED )
} catch ( ex : CancellationException ) {
debug ( " device sleep timeout cancelled " )
}
}
2020-05-31 11:23:25 -07:00
// broadcast an intent with our new connection state
2020-10-01 22:20:19 +02:00
serviceBroadcasts . broadcastConnection ( )
2020-04-04 15:29:16 -07:00
}
fun startDisconnect ( ) {
2020-04-19 17:25:20 -07:00
// Just in case the user uncleanly reboots the phone, save now (we normally save in onDestroy)
saveSettings ( )
2023-01-17 18:46:04 -03:00
stopPacketQueue ( )
2022-05-20 09:13:59 -03:00
stopLocationRequests ( )
2020-04-04 15:29:16 -07:00
GeeksvilleApplication . analytics . track (
" mesh_disconnect " ,
DataPair ( " num_nodes " , numNodes ) ,
DataPair ( " num_online " , numOnlineNodes )
)
2020-04-21 14:46:52 -07:00
GeeksvilleApplication . analytics . track ( " num_nodes " , DataPair ( numNodes ) )
2020-05-31 11:23:25 -07:00
// broadcast an intent with our new connection state
2020-10-01 22:20:19 +02:00
serviceBroadcasts . broadcastConnection ( )
2020-04-04 15:29:16 -07:00
}
fun startConnect ( ) {
2020-02-04 12:12:29 -08:00
// Do our startup init
2020-02-17 18:46:20 -08:00
try {
2020-04-21 14:46:52 -07:00
connectTimeMsec = System . currentTimeMillis ( )
2021-02-08 11:22:15 +08:00
SoftwareUpdateService . sendProgress (
this ,
2022-05-20 09:12:55 -03:00
SoftwareUpdateService . ProgressNotStarted ,
2021-02-08 11:22:15 +08:00
true
) // Kinda crufty way of reiniting software update
2020-06-05 21:12:15 -07:00
startConfig ( )
2020-02-25 09:28:47 -08:00
2020-05-11 11:44:24 -07:00
} catch ( ex : InvalidProtocolBufferException ) {
errormsg (
" Invalid protocol buffer sent by device - update device software and try again " ,
ex
)
2020-04-24 15:59:01 -07:00
} catch ( ex : RadioNotConnectedException ) {
// note: no need to call startDeviceSleep(), because this exception could only have reached us if it was already called
2020-05-11 11:44:24 -07:00
errormsg ( " Lost connection to radio during init - waiting for reconnect " )
2020-02-17 18:46:20 -08:00
} catch ( ex : RemoteException ) {
// It seems that when the ESP32 goes offline it can briefly come back for a 100ms ish which
// causes the phone to try and reconnect. If we fail downloading our initial radio state we don't want to
// claim we have a valid connection still
2020-04-04 15:29:16 -07:00
connectionState = ConnectionState . DEVICE _SLEEP
startDeviceSleep ( )
2021-03-27 15:50:16 +08:00
throw ex // Important to rethrow so that we don't tell the app all is well
2020-02-04 12:12:29 -08:00
}
2020-04-04 15:29:16 -07:00
}
2020-02-25 09:28:47 -08:00
2020-04-04 15:29:16 -07:00
// Cancel any existing timeouts
sleepTimeout ?. let {
it . cancel ( )
sleepTimeout = null
}
connectionState = c
when ( c ) {
ConnectionState . CONNECTED ->
startConnect ( )
ConnectionState . DEVICE _SLEEP ->
startDeviceSleep ( )
ConnectionState . DISCONNECTED ->
startDisconnect ( )
2020-02-04 12:12:29 -08:00
}
2020-02-28 13:53:16 -08:00
2020-05-31 11:23:25 -07:00
// Update the android notification in the status bar
2021-02-07 17:38:54 -08:00
maybeUpdateServiceStatusNotification ( )
}
private fun maybeUpdateServiceStatusNotification ( ) {
val currentSummary = notificationSummary
if ( previousSummary == null || ! previousSummary . equals ( currentSummary ) ) {
serviceNotifications . updateServiceStateNotification ( currentSummary )
previousSummary = currentSummary
}
2020-05-31 11:23:25 -07:00
}
2022-04-22 17:39:48 -07:00
private fun onRadioConnectionState ( state : RadioServiceConnectionState ) {
2022-10-05 22:06:46 -03:00
// sleep now disabled by default on ESP32, permanent is true unless light sleep enabled
val isRouter = localConfig . device . role == ConfigProtos . Config . DeviceConfig . Role . ROUTER
val lsEnabled = localConfig . power . isPowerSaving || isRouter
2022-04-22 17:39:48 -07:00
val connected = state . isConnected
val permanent = state . isPermanent || ! lsEnabled
onConnectionChanged (
when {
connected -> ConnectionState . CONNECTED
permanent -> ConnectionState . DISCONNECTED
else -> ConnectionState . DEVICE _SLEEP
}
)
}
2020-02-04 12:12:29 -08:00
2022-04-22 17:39:48 -07:00
private fun onReceiveFromRadio ( bytes : ByteArray ) {
try {
2022-06-20 22:46:45 -03:00
val proto = MeshProtos . FromRadio . parseFrom ( bytes )
2022-04-22 17:39:48 -07:00
// info("Received from radio service: ${proto.toOneLineString()}")
when ( proto . payloadVariantCase . number ) {
2022-06-20 22:46:45 -03:00
MeshProtos . FromRadio . PACKET _FIELD _NUMBER -> handleReceivedMeshPacket ( proto . packet )
MeshProtos . FromRadio . CONFIG _COMPLETE _ID _FIELD _NUMBER -> handleConfigComplete ( proto . configCompleteId )
2022-04-22 17:39:48 -07:00
MeshProtos . FromRadio . MY _INFO _FIELD _NUMBER -> handleMyInfo ( proto . myInfo )
MeshProtos . FromRadio . NODE _INFO _FIELD _NUMBER -> handleNodeInfo ( proto . nodeInfo )
2022-10-16 19:19:03 -03:00
MeshProtos . FromRadio . CHANNEL _FIELD _NUMBER -> handleChannel ( proto . channel )
2022-06-20 22:46:45 -03:00
MeshProtos . FromRadio . CONFIG _FIELD _NUMBER -> handleDeviceConfig ( proto . config )
2022-09-13 22:59:50 -03:00
MeshProtos . FromRadio . MODULECONFIG _FIELD _NUMBER -> handleModuleConfig ( proto . moduleConfig )
2023-01-17 18:46:04 -03:00
MeshProtos . FromRadio . QUEUESTATUS _FIELD _NUMBER -> handleQueueStatus ( proto . queueStatus )
2022-04-22 17:39:48 -07:00
else -> errormsg ( " Unexpected FromRadio variant " )
2020-01-24 22:22:30 -08:00
}
2022-04-22 17:39:48 -07:00
} catch ( ex : InvalidProtocolBufferException ) {
errormsg ( " Invalid Protobuf from radio, len= ${bytes.size} " , ex )
2020-01-24 17:05:55 -08:00
}
}
2020-04-22 18:34:22 -07:00
/// A provisional MyNodeInfo that we will install if all of our node config downloads go okay
private var newMyNodeInfo : MyNodeInfo ? = null
/// provisional NodeInfos we will install if all goes well
private val newNodes = mutableListOf < MeshProtos . NodeInfo > ( )
/// Used to make sure we never get foold by old BLE packets
private var configNonce = 1
2022-06-20 22:46:45 -03:00
private fun handleDeviceConfig ( config : ConfigProtos . Config ) {
debug ( " Received config ${config.toOneLineString()} " )
2022-09-13 22:49:38 -03:00
val packetToSave = MeshLog (
2022-06-20 22:46:45 -03:00
UUID . randomUUID ( ) . toString ( ) ,
" Config ${config.payloadVariantCase} " ,
System . currentTimeMillis ( ) ,
config . toString ( )
)
2022-09-13 22:49:38 -03:00
insertMeshLog ( packetToSave )
2022-06-20 22:46:45 -03:00
setLocalConfig ( config )
}
2022-11-22 22:01:37 -03:00
private fun handleModuleConfig ( config : ModuleConfigProtos . ModuleConfig ) {
debug ( " Received moduleConfig ${config.toOneLineString()} " )
2022-09-13 22:59:50 -03:00
val packetToSave = MeshLog (
UUID . randomUUID ( ) . toString ( ) ,
2022-11-22 22:01:37 -03:00
" ModuleConfig ${config.payloadVariantCase} " ,
2022-09-13 22:59:50 -03:00
System . currentTimeMillis ( ) ,
2022-11-22 22:01:37 -03:00
config . toString ( )
2022-09-13 22:59:50 -03:00
)
insertMeshLog ( packetToSave )
2022-11-22 22:01:37 -03:00
setLocalModuleConfig ( config )
2022-09-13 22:59:50 -03:00
}
2023-01-17 18:46:04 -03:00
private fun handleQueueStatus ( queueStatus : MeshProtos . QueueStatus ) {
debug ( " queueStatus ${queueStatus.toOneLineString()} " )
val ( success , isFull , requestId ) = with ( queueStatus ) {
2023-03-08 08:07:47 -03:00
Triple ( res == 0 , free == 0 , meshPacketId )
2023-01-17 18:46:04 -03:00
}
if ( success && isFull ) return // Queue is full, wait for free != 0
if ( requestId != 0 ) queueResponse . remove ( requestId ) ?. complete ( success )
else queueResponse . entries . lastOrNull { ! it . value . isDone } ?. value ?. complete ( success )
}
2022-10-16 19:19:03 -03:00
private fun handleChannel ( ch : ChannelProtos . Channel ) {
debug ( " Received channel ${ch.index} " )
val packetToSave = MeshLog (
UUID . randomUUID ( ) . toString ( ) ,
" Channel " ,
System . currentTimeMillis ( ) ,
ch . toString ( )
)
insertMeshLog ( packetToSave )
2023-05-06 08:08:17 -03:00
if ( ch . role != ChannelProtos . Channel . Role . DISABLED ) updateChannelSettings ( ch )
2022-10-16 19:19:03 -03:00
}
2020-04-22 18:34:22 -07:00
/ * *
* Convert a protobuf NodeInfo into our model objects and update our node DB
* /
private fun installNodeInfo ( info : MeshProtos . NodeInfo ) {
// Just replace/add any entry
updateNodeInfo ( info . num ) {
if ( info . hasUser ( ) )
it . user =
MeshUser (
info . user . id ,
info . user . longName ,
2021-03-14 11:42:04 +08:00
info . user . shortName ,
2022-11-08 23:11:18 -03:00
info . user . hwModel ,
info . user . isLicensed
2020-04-22 18:34:22 -07:00
)
if ( info . hasPosition ( ) ) {
// For the local node, it might not be able to update its times because it doesn't have a valid GPS reading yet
// so if the info is for _our_ node we always assume time is current
2020-05-04 08:05:59 -07:00
it . position = Position ( info . position )
2020-04-22 18:34:22 -07:00
}
2021-12-25 18:37:18 -03:00
2022-04-04 19:10:15 -03:00
if ( info . hasDeviceMetrics ( ) ) {
it . deviceMetrics = DeviceMetrics ( info . deviceMetrics )
2022-03-28 15:50:33 -03:00
}
2021-12-25 18:37:18 -03:00
it . lastHeard = info . lastHeard
2020-04-22 18:34:22 -07:00
}
}
private fun handleNodeInfo ( info : MeshProtos . NodeInfo ) {
2022-04-04 19:10:15 -03:00
debug ( " Received nodeinfo num= ${info.num} , hasUser= ${info.hasUser()} , hasPosition= ${info.hasPosition()} , hasDeviceMetrics= ${info.hasDeviceMetrics()} " )
2020-04-22 18:34:22 -07:00
2022-09-13 22:49:38 -03:00
val packetToSave = MeshLog (
2020-10-01 16:59:34 -04:00
UUID . randomUUID ( ) . toString ( ) ,
" NodeInfo " ,
System . currentTimeMillis ( ) ,
info . toString ( )
)
2022-09-13 22:49:38 -03:00
insertMeshLog ( packetToSave )
2020-09-23 22:47:45 -04:00
2020-04-22 18:34:22 -07:00
logAssert ( newNodes . size <= 256 ) // Sanity check to make sure a device bug can't fill this list forever
newNodes . add ( info )
}
2021-03-14 11:42:04 +08:00
private var rawMyNodeInfo : MeshProtos . MyNodeInfo ? = null
/ * * Regenerate the myNodeInfo model . We call this twice . Once after we receive myNodeInfo from the device
* and again after we have the node DB ( which might allow us a better notion of our HwModel .
* /
private fun regenMyNodeInfo ( ) {
val myInfo = rawMyNodeInfo
if ( myInfo != null ) {
2022-05-20 14:26:03 -07:00
val a = radioInterfaceService . getBondedDeviceAddress ( )
2021-03-14 11:42:04 +08:00
val isBluetoothInterface = a != null && a . startsWith ( " x " )
2022-02-15 20:12:04 -03:00
val nodeNum =
myInfo . myNodeNum // Note: can't use the normal property because myNodeInfo not yet setup
val ni = nodeDBbyNodeNum [ nodeNum ] // can't use toNodeInfo because too early
val hwModelStr = ni ?. user ?. hwModelString
2022-03-11 00:12:48 -03:00
setFirmwareUpdateFilename ( hwModelStr )
2021-03-14 11:42:04 +08:00
val mi = with ( myInfo ) {
MyNodeInfo (
myNodeNum ,
hasGps ,
hwModelStr ,
firmwareVersion ,
2022-03-11 00:12:48 -03:00
firmwareUpdateFilename ?. appLoad != null && firmwareUpdateFilename ?. littlefs != null ,
2022-01-03 21:59:30 -03:00
isBluetoothInterface && SoftwareUpdateService . shouldUpdate (
2021-03-14 11:42:04 +08:00
this @MeshService ,
DeviceVersion ( firmwareVersion )
) ,
2022-05-20 09:12:55 -03:00
currentPacketId and 0xffffffffL ,
2021-03-14 11:42:04 +08:00
if ( messageTimeoutMsec == 0 ) 5 * 60 * 1000 else messageTimeoutMsec , // constants from current device code
minAppVersion ,
2022-01-26 22:26:35 -03:00
maxChannels ,
2022-08-28 07:54:47 -03:00
hasWifi ,
2022-01-26 22:26:35 -03:00
channelUtilization ,
airUtilTx
2021-03-14 11:42:04 +08:00
)
}
newMyNodeInfo = mi
}
}
private fun sendAnalytics ( ) {
val myInfo = rawMyNodeInfo
val mi = myNodeInfo
if ( myInfo != null && mi != null ) {
/// Track types of devices and firmware versions in use
GeeksvilleApplication . analytics . setUserInfo (
DataPair ( " firmware " , mi . firmwareVersion ) ,
DataPair ( " has_gps " , mi . hasGPS ) ,
DataPair ( " hw_model " , mi . model ) ,
DataPair ( " dev_error_count " , myInfo . errorCount )
)
2022-09-18 18:35:13 -03:00
if ( myInfo . errorCode != MeshProtos . CriticalErrorCode . UNSPECIFIED && myInfo . errorCode != MeshProtos . CriticalErrorCode . NONE ) {
2021-03-14 11:42:04 +08:00
GeeksvilleApplication . analytics . track (
" dev_error " ,
DataPair ( " code " , myInfo . errorCode . number ) ,
DataPair ( " address " , myInfo . errorAddress ) ,
// We also include this info, because it is required to correctly decode address from the map file
DataPair ( " firmware " , mi . firmwareVersion ) ,
DataPair ( " hw_model " , mi . model )
)
}
}
}
2020-05-30 17:28:00 -07:00
/ * *
* Update the nodeinfo ( called from either new API version or the old one )
* /
2020-04-22 18:34:22 -07:00
private fun handleMyInfo ( myInfo : MeshProtos . MyNodeInfo ) {
2022-09-13 22:49:38 -03:00
val packetToSave = MeshLog (
2020-10-01 16:59:34 -04:00
UUID . randomUUID ( ) . toString ( ) ,
" MyNodeInfo " ,
System . currentTimeMillis ( ) ,
myInfo . toString ( )
)
2022-09-13 22:49:38 -03:00
insertMeshLog ( packetToSave )
2020-09-23 22:47:45 -04:00
2021-03-14 11:42:04 +08:00
rawMyNodeInfo = myInfo
regenMyNodeInfo ( )
2020-04-22 18:34:22 -07:00
2021-03-02 22:12:42 +08:00
// We'll need to get a new set of channels and settings now
2022-09-12 19:07:30 -03:00
serviceScope . handledLaunch {
2023-05-20 11:42:15 -03:00
radioConfigRepository . clearChannelSet ( )
radioConfigRepository . clearLocalConfig ( )
radioConfigRepository . clearLocalModuleConfig ( )
2021-03-24 15:19:26 +08:00
}
2020-04-22 18:34:22 -07:00
}
2021-12-25 19:30:45 -03:00
/// If we've received our initial config, our radio settings and all of our channels, send any queued packets and broadcast connected to clients
2021-03-02 22:12:42 +08:00
private fun onHasSettings ( ) {
2021-03-03 07:49:23 +08:00
2021-12-25 19:30:45 -03:00
processQueuedPackets ( ) // send any packets that were queued up
2021-03-02 22:12:42 +08:00
// broadcast an intent with our new connection state
serviceBroadcasts . broadcastConnection ( )
onNodeDBChanged ( )
reportConnection ( )
}
2020-04-22 18:34:22 -07:00
private fun handleConfigComplete ( configCompleteId : Int ) {
if ( configCompleteId == configNonce ) {
2020-09-23 22:47:45 -04:00
2022-09-13 22:49:38 -03:00
val packetToSave = MeshLog (
2020-10-01 16:59:34 -04:00
UUID . randomUUID ( ) . toString ( ) ,
" ConfigComplete " ,
System . currentTimeMillis ( ) ,
configCompleteId . toString ( )
)
2022-09-13 22:49:38 -03:00
insertMeshLog ( packetToSave )
2020-09-23 22:47:45 -04:00
2020-04-22 18:34:22 -07:00
// This was our config request
if ( newMyNodeInfo == null || newNodes . isEmpty ( ) )
2020-07-01 15:47:58 -07:00
errormsg ( " Did not receive a valid config " )
2020-04-22 18:34:22 -07:00
else {
discardNodeDB ( )
2021-02-01 10:31:39 +08:00
debug ( " Installing new node DB " )
2022-09-30 15:57:04 -03:00
myNodeInfo = newMyNodeInfo // Install myNodeInfo as current
2020-04-22 18:34:22 -07:00
newNodes . forEach ( :: installNodeInfo )
newNodes . clear ( ) // Just to save RAM ;-)
haveNodeDB = true // we now have nodes from real hardware
2021-03-14 11:42:04 +08:00
regenMyNodeInfo ( ) // we have a node db now, so can possibly find a better hwmodel
2021-03-15 10:31:44 +08:00
myNodeInfo = newMyNodeInfo // we might have just updated myNodeInfo
2021-03-19 23:14:46 +08:00
2021-03-14 11:42:04 +08:00
sendAnalytics ( )
2022-08-29 12:50:29 -03:00
if ( deviceVersion < minDeviceVersion || appVersion < minAppVersion ) {
info ( " Device firmware or app is too old, faking config so firmware update can occur " )
2022-06-20 22:46:45 -03:00
clearLocalConfig ( )
2022-10-16 19:19:03 -03:00
}
onHasSettings ( )
2020-04-22 18:34:22 -07:00
}
} else
warn ( " Ignoring stale config complete " )
}
2022-10-10 18:06:19 -03:00
private fun requestConfig ( config : AdminProtos . AdminMessage . ConfigType ) {
sendToRadio ( newMeshPacketTo ( myNodeNum ) . buildAdminPacket ( wantResponse = true ) {
getConfigRequest = config
} )
}
private fun requestAllConfig ( ) {
AdminProtos . AdminMessage . ConfigType . values ( ) . filter {
it != AdminProtos . AdminMessage . ConfigType . UNRECOGNIZED
} . forEach ( :: requestConfig )
2021-03-02 22:12:42 +08:00
}
2020-04-22 18:34:22 -07:00
/ * *
* Start the modern ( REV2 ) API configuration flow
* /
private fun startConfig ( ) {
configNonce += 1
newNodes . clear ( )
newMyNodeInfo = null
2022-10-12 23:40:54 -03:00
if ( BluetoothInterface . invalidVersion ) onHasSettings ( ) // Device firmware is too old
2020-06-09 09:53:32 -07:00
debug ( " Starting config nonce= $configNonce " )
2020-04-22 18:34:22 -07:00
2020-04-23 11:02:44 -07:00
sendToRadio ( ToRadio . newBuilder ( ) . apply {
this . wantConfigId = configNonce
} )
2020-04-22 18:34:22 -07:00
}
2020-10-01 22:20:19 +02:00
/ * *
* Send a position ( typically from our built in GPS ) into the mesh .
* /
2020-02-19 18:51:59 -08:00
private fun sendPosition (
2021-03-20 11:23:31 +08:00
lat : Double = 0.0 ,
lon : Double = 0.0 ,
alt : Int = 0 ,
2022-10-07 21:38:53 -03:00
time : Int = currentSecond ( ) ,
2022-11-06 17:46:57 -03:00
destNum : Int ? = null ,
wantResponse : Boolean = false
2020-02-19 18:51:59 -08:00
) {
2021-03-27 15:50:16 +08:00
try {
2021-03-28 10:33:59 +08:00
val mi = myNodeInfo
2021-03-29 20:33:06 +08:00
if ( mi != null ) {
2022-11-06 17:46:57 -03:00
val idNum = destNum ?: mi . myNodeNum // when null we just send to the local node
debug ( " Sending our position/time to= $idNum lat= ${lat.anonymize} , lon= ${lon.anonymize} , alt= $alt , time= $time " )
2020-02-16 14:22:24 -08:00
2021-03-28 10:33:59 +08:00
val position = MeshProtos . Position . newBuilder ( ) . also {
it . longitudeI = Position . degI ( lon )
it . latitudeI = Position . degI ( lat )
2020-05-04 08:05:59 -07:00
2021-03-28 10:33:59 +08:00
it . altitude = alt
2022-10-07 21:38:53 -03:00
it . time = time
2021-03-28 10:33:59 +08:00
} . build ( )
2020-02-16 14:22:24 -08:00
2021-03-28 10:33:59 +08:00
// Also update our own map for our nodenum, by handling the packet just like packets from other users
2021-03-28 11:05:39 +08:00
handleReceivedPosition ( mi . myNodeNum , position )
2021-03-02 16:27:43 +08:00
2021-03-28 10:33:59 +08:00
val fullPacket =
2022-11-06 17:46:57 -03:00
newMeshPacketTo ( idNum ) . buildMeshPacket ( priority = MeshPacket . Priority . BACKGROUND ) {
2021-03-28 10:33:59 +08:00
// Use the new position as data format
portnumValue = Portnums . PortNum . POSITION _APP _VALUE
payload = position . toByteString ( )
2022-11-06 17:46:57 -03:00
this . wantResponse = wantResponse
2021-03-28 10:33:59 +08:00
}
2020-04-04 15:29:16 -07:00
2021-03-28 10:33:59 +08:00
// send the packet into the mesh
sendToRadio ( fullPacket )
}
2021-03-20 11:23:31 +08:00
} catch ( ex : BLEException ) {
2021-01-05 14:01:45 +08:00
warn ( " Ignoring disconnected radio during gps location update " )
}
2020-10-01 22:20:19 +02:00
}
2020-04-23 11:18:48 -07:00
/ * *
2023-03-27 15:30:42 -03:00
* Send setOwner admin packet with [ MeshProtos . User ] protobuf
2020-04-23 11:18:48 -07:00
* /
2023-03-27 15:30:42 -03:00
fun setOwner ( meshUser : MeshUser ) = with ( meshUser ) {
val dest = nodeDBbyID [ id ]
if ( dest != null ) {
val old = dest . user
if ( longName == old ?. longName && shortName == old . shortName && isLicensed == old . isLicensed )
2020-06-13 16:02:57 -07:00
debug ( " Ignoring nop owner change " )
else {
2023-03-27 15:30:42 -03:00
debug ( " SetOwner Id: $id longName: ${longName.anonymize} shortName: $shortName isLicensed: $isLicensed " )
2020-04-23 11:18:48 -07:00
2020-06-13 16:02:57 -07:00
val user = MeshProtos . User . newBuilder ( ) . also {
it . longName = longName
it . shortName = shortName
2023-03-28 22:31:36 -03:00
it . hwModel = hwModel
2022-11-08 23:11:18 -03:00
it . isLicensed = isLicensed
2020-06-13 16:02:57 -07:00
} . build ( )
// Also update our own map for our nodenum, by handling the packet just like packets from other users
2023-03-27 15:30:42 -03:00
handleReceivedUser ( dest . num , user )
2020-06-13 16:02:57 -07:00
2021-02-27 11:13:30 +08:00
// encapsulate our payload in the proper protobufs and fire it off
2023-03-27 15:30:42 -03:00
val packet = newMeshPacketTo ( dest . num ) . buildAdminPacket {
2021-03-02 16:27:43 +08:00
setOwner = user
}
2021-02-27 11:13:30 +08:00
// send the packet into the mesh
2021-03-02 16:27:43 +08:00
sendToRadio ( packet )
2020-06-13 16:02:57 -07:00
}
} else
throw Exception ( " Can't set user without a node info " ) // this shouldn't happen
2020-04-23 11:18:48 -07:00
}
2021-03-02 22:12:42 +08:00
2020-05-30 17:28:00 -07:00
/// Do not use directly, instead call generatePacketId()
2021-03-02 22:12:42 +08:00
private var currentPacketId = Random ( System . currentTimeMillis ( ) ) . nextLong ( ) . absoluteValue
2020-05-30 17:28:00 -07:00
/ * *
* Generate a unique packet ID ( if we know enough to do so - otherwise return 0 so the device will do it )
* /
2021-03-02 22:12:42 +08:00
@Synchronized
2020-05-30 17:28:00 -07:00
private fun generatePacketId ( ) : Int {
2021-03-02 22:12:42 +08:00
val numPacketIds =
2022-05-20 09:12:55 -03:00
( ( 1L shl 32 ) - 1 ) // A mask for only the valid packet ID bits, either 255 or maxint
2020-05-30 17:28:00 -07:00
2021-03-02 22:12:42 +08:00
currentPacketId ++
2021-02-12 13:50:39 +08:00
2021-03-02 22:12:42 +08:00
currentPacketId = currentPacketId and 0xffffffff // keep from exceeding 32 bits
2020-05-30 17:28:00 -07:00
2021-03-02 22:12:42 +08:00
// Use modulus and +1 to ensure we skip 0 on any values we return
return ( ( currentPacketId % numPacketIds ) + 1L ) . toInt ( )
2020-05-30 17:28:00 -07:00
}
2022-09-16 18:17:19 -03:00
private var firmwareUpdateFilename : UpdateFilenames ? = null
2020-05-13 17:00:23 -07:00
2020-05-13 14:47:55 -07:00
/ * * *
* Return the filename we will install on the device
* /
2022-03-11 00:12:48 -03:00
private fun setFirmwareUpdateFilename ( model : String ? ) {
2020-05-13 17:00:23 -07:00
firmwareUpdateFilename = try {
2022-03-11 00:12:48 -03:00
if ( model != null )
2020-05-13 17:00:23 -07:00
SoftwareUpdateService . getUpdateFilename (
this ,
2022-03-11 00:12:48 -03:00
model
2020-05-13 17:00:23 -07:00
)
else
2020-05-13 14:47:55 -07:00
null
2020-05-13 17:00:23 -07:00
} catch ( ex : Exception ) {
errormsg ( " Unable to update " , ex )
null
}
2020-07-02 10:21:14 -07:00
debug ( " setFirmwareUpdateFilename $firmwareUpdateFilename " )
2020-05-13 17:00:23 -07:00
}
2020-05-13 14:47:55 -07:00
2020-10-21 17:51:30 +08:00
/// We only allow one update to be running at a time
private var updateJob : Job ? = null
2020-05-13 14:47:55 -07:00
private fun doFirmwareUpdate ( ) {
// Run in the IO thread
val filename = firmwareUpdateFilename ?: throw Exception ( " No update filename " )
val safe =
2020-06-07 17:11:30 -07:00
BluetoothInterface . safe
2020-06-04 12:34:34 -07:00
?: throw Exception ( " Can't update - no bluetooth connected " )
2020-05-13 14:47:55 -07:00
2021-02-12 13:50:39 +08:00
if ( updateJob ?. isActive == true ) {
errormsg ( " A firmware update is already running " )
2020-10-21 17:51:30 +08:00
throw Exception ( " Firmware update already running " )
2021-02-12 13:50:39 +08:00
} else {
debug ( " Creating firmware update coroutine " )
2020-10-21 17:51:30 +08:00
updateJob = serviceScope . handledLaunch {
2021-03-20 18:47:12 +08:00
exceptionReporter {
debug ( " Starting firmware update coroutine " )
SoftwareUpdateService . doUpdate ( this @MeshService , safe , filename )
}
2020-10-21 17:51:30 +08:00
}
2021-02-12 13:50:39 +08:00
}
2020-05-13 14:47:55 -07:00
}
2021-03-20 17:47:02 +08:00
private val binder = object : IMeshService . Stub ( ) {
2020-04-19 19:23:20 -07:00
2020-04-20 15:38:53 -07:00
override fun setDeviceAddress ( deviceAddr : String ? ) = toRemoteExceptions {
2020-07-07 10:44:06 -07:00
debug ( " Passing through device change to radio service: ${deviceAddr.anonymize} " )
2020-07-04 16:54:48 -07:00
2022-04-22 17:39:48 -07:00
val res = radioInterfaceService . setDeviceAddress ( deviceAddr )
2020-07-04 16:54:48 -07:00
if ( res ) {
discardNodeDB ( )
2022-11-29 17:42:16 -03:00
} else {
serviceBroadcasts . broadcastConnection ( )
2020-07-04 16:54:48 -07:00
}
res
2020-04-19 19:23:20 -07:00
}
2020-01-25 10:00:57 -08:00
// Note: bound methods don't get properly exception caught/logged, so do that with a wrapper
// per https://blog.classycode.com/dealing-with-exceptions-in-aidl-9ba904c6d63
2020-01-26 11:33:51 -08:00
override fun subscribeReceiver ( packageName : String , receiverName : String ) =
toRemoteExceptions {
clientPackages [ receiverName ] = packageName
}
2020-01-22 21:25:31 -08:00
2020-05-13 14:47:55 -07:00
override fun getUpdateStatus ( ) : Int = SoftwareUpdateService . progress
override fun startFirmwareUpdate ( ) = toRemoteExceptions {
doFirmwareUpdate ( )
}
2021-03-04 09:08:29 +08:00
override fun getMyNodeInfo ( ) : MyNodeInfo ? = this @MeshService . myNodeInfo
2020-05-13 14:47:55 -07:00
2020-02-17 13:14:53 -08:00
override fun getMyId ( ) = toRemoteExceptions { myNodeID }
2023-02-01 12:16:44 -03:00
override fun getPacketId ( ) = toRemoteExceptions { generatePacketId ( ) }
2023-03-27 15:30:42 -03:00
override fun setOwner ( user : MeshUser ) = toRemoteExceptions {
this @MeshService . setOwner ( user )
}
2020-01-25 10:00:57 -08:00
2023-04-22 12:06:25 -03:00
override fun setRemoteOwner ( destNum : Int , payload : ByteArray ) = toRemoteExceptions {
val parsed = MeshProtos . User . parseFrom ( payload )
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket {
setOwner = parsed
} )
}
override fun getRemoteOwner ( id : Int , destNum : Int ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket ( id = id , wantResponse = true ) {
getOwnerRequest = true
} )
}
2020-10-01 22:20:19 +02:00
override fun send ( p : DataPacket ) {
2020-01-26 10:44:42 -08:00
toRemoteExceptions {
2023-01-12 17:25:28 -03:00
if ( p . id == 0 ) p . id = generatePacketId ( )
2020-05-30 15:48:50 -07:00
2020-05-30 19:58:36 -07:00
info ( " sendData dest= ${p.to} , id= ${p.id} <- ${p.bytes!!.size} bytes (connectionState= $connectionState ) " )
2020-05-30 15:48:50 -07:00
2021-03-14 11:42:04 +08:00
if ( p . dataType == 0 )
2021-03-07 09:57:14 +08:00
throw Exception ( " Port numbers must be non-zero! " ) // we are now more strict
2020-12-07 20:33:29 +08:00
if ( p . bytes . size >= MeshProtos . Constants . DATA_PAYLOAD_LEN . number ) {
2020-11-21 08:41:26 +08:00
p . status = MessageStatus . ERROR
throw RemoteException ( " Message too long " )
2023-01-12 17:25:28 -03:00
} else p . status = MessageStatus . QUEUED
2020-11-21 08:41:26 +08:00
2023-01-12 17:25:28 -03:00
if ( connectionState == ConnectionState . CONNECTED ) try {
sendNow ( p )
} catch ( ex : Exception ) {
errormsg ( " Error sending message, so enqueueing " , ex )
2020-04-04 15:29:16 -07:00
}
2022-12-10 00:14:32 -03:00
serviceBroadcasts . broadcastMessageStatus ( p )
2023-01-12 17:25:28 -03:00
// Keep a record of DataPackets, so GUIs can show proper chat history
2022-12-10 00:14:32 -03:00
rememberDataPacket ( p )
2020-03-08 15:22:31 -07:00
GeeksvilleApplication . analytics . track (
" data_send " ,
2020-05-30 17:28:00 -07:00
DataPair ( " num_bytes " , p . bytes . size ) ,
2020-05-30 15:48:50 -07:00
DataPair ( " type " , p . dataType )
2020-03-08 15:22:31 -07:00
)
2020-04-22 07:59:07 -07:00
GeeksvilleApplication . analytics . track (
" num_data_sent " ,
DataPair ( 1 )
)
2020-01-25 10:00:57 -08:00
}
2020-05-30 15:48:50 -07:00
}
2020-01-22 21:25:31 -08:00
2022-11-29 17:47:49 -03:00
override fun getConfig ( ) : ByteArray = toRemoteExceptions {
this @MeshService . localConfig . toByteArray ( ) ?: throw NoDeviceConfigException ( )
}
2023-04-22 12:06:25 -03:00
/ * * Send our current radio config to the device
* /
2022-10-11 16:27:36 -03:00
override fun setConfig ( payload : ByteArray ) = toRemoteExceptions {
2023-04-22 12:06:25 -03:00
setRemoteConfig ( myNodeNum , payload )
}
override fun setRemoteConfig ( destNum : Int , payload : ByteArray ) = toRemoteExceptions {
debug ( " Setting new radio config! " )
val config = ConfigProtos . Config . parseFrom ( payload )
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket { setConfig = config } )
if ( destNum == myNodeNum ) setLocalConfig ( config ) // Update our local copy
}
override fun getRemoteConfig ( id : Int , destNum : Int , config : Int ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket ( id = id , wantResponse = true ) {
getConfigRequestValue = config
} )
2021-02-27 11:44:05 +08:00
}
2023-04-22 12:06:25 -03:00
/ * * Send our current module config to the device
* /
override fun setModuleConfig ( destNum : Int , payload : ByteArray ) = toRemoteExceptions {
debug ( " Setting new module config! " )
val config = ModuleConfigProtos . ModuleConfig . parseFrom ( payload )
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket { setModuleConfig = config } )
if ( destNum == myNodeNum ) setLocalModuleConfig ( config ) // Update our local copy
}
override fun getModuleConfig ( id : Int , destNum : Int , config : Int ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket ( id = id , wantResponse = true ) {
getModuleConfigRequestValue = config
} )
}
override fun setRingtone ( destNum : Int , ringtone : String ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket {
setRingtoneMessage = ringtone
} )
}
override fun getRingtone ( id : Int , destNum : Int ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket ( id = id , wantResponse = true ) {
getRingtoneRequest = true
} )
}
override fun setCannedMessages ( destNum : Int , messages : String ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket {
setCannedMessageModuleMessages = messages
} )
}
override fun getCannedMessages ( id : Int , destNum : Int ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket ( id = id , wantResponse = true ) {
getCannedMessageModuleMessagesRequest = true
} )
2022-11-22 22:01:37 -03:00
}
2022-10-11 16:27:36 -03:00
override fun setChannel ( payload : ByteArray ? ) = toRemoteExceptions {
2023-04-29 07:14:30 -03:00
setRemoteChannel ( myNodeNum , payload )
}
override fun setRemoteChannel ( destNum : Int , payload : ByteArray ? ) = toRemoteExceptions {
val channel = ChannelProtos . Channel . parseFrom ( payload )
2023-05-06 08:08:17 -03:00
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket { setChannel = channel } )
2023-04-29 07:14:30 -03:00
}
override fun getRemoteChannel ( id : Int , destNum : Int , index : Int ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket ( id = id , wantResponse = true ) {
getChannelRequest = index + 1
} )
2021-02-27 13:43:55 +08:00
}
2022-11-29 17:47:49 -03:00
override fun beginEditSettings ( ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( myNodeNum ) . buildAdminPacket {
beginEditSettings = true
} )
}
override fun commitEditSettings ( ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( myNodeNum ) . buildAdminPacket {
commitEditSettings = true
} )
}
2023-02-20 21:48:55 -03:00
override fun getChannelSet ( ) : ByteArray = toRemoteExceptions {
this @MeshService . channelSet . toByteArray ( )
}
2020-04-19 11:56:06 -07:00
override fun getNodes ( ) : MutableList < NodeInfo > = toRemoteExceptions {
val r = nodeDBbyID . values . toMutableList ( )
2020-01-24 20:46:29 -08:00
info ( " in getOnline, count= ${r.size} " )
2020-01-24 20:35:42 -08:00
// return arrayOf("+16508675309")
2020-01-25 10:00:57 -08:00
r
2020-01-22 21:25:31 -08:00
}
2020-04-04 15:29:16 -07:00
override fun connectionState ( ) : String = toRemoteExceptions {
val r = this @MeshService . connectionState
info ( " in connectionState= $r " )
r . toString ( )
2020-01-22 21:25:31 -08:00
}
2022-01-03 21:59:30 -03:00
2022-05-20 09:13:59 -03:00
override fun startProvideLocation ( ) = toRemoteExceptions {
startLocationRequests ( )
2022-01-03 21:59:30 -03:00
}
override fun stopProvideLocation ( ) = toRemoteExceptions {
stopLocationRequests ( )
}
2022-06-06 17:29:09 -03:00
2023-04-22 12:06:25 -03:00
override fun requestPosition ( destNum : Int , position : Position ) = toRemoteExceptions {
if ( position == Position ( 0.0 , 0.0 , 0 ) ) {
2022-11-15 22:00:29 -03:00
// request position
2023-05-10 22:17:09 -03:00
sendPosition ( destNum = destNum , wantResponse = true )
2023-04-22 12:06:25 -03:00
} else {
2023-05-12 18:34:29 -03:00
// send fixed position (local only/no remote method, so we force destNum to null)
2023-04-22 12:06:25 -03:00
val ( lat , lon , alt ) = position
2023-05-12 18:34:29 -03:00
sendPosition ( destNum = null , lat = lat , lon = lon , alt = alt )
2022-11-15 22:00:29 -03:00
}
2023-04-22 12:06:25 -03:00
}
2022-11-06 17:46:57 -03:00
2023-04-16 06:16:41 -03:00
override fun requestTraceroute ( requestId : Int , destNum : Int ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildMeshPacket ( id = requestId ) {
portnumValue = Portnums . PortNum . TRACEROUTE _APP _VALUE
payload = routeDiscovery { } . toByteString ( )
wantResponse = true
} )
}
2023-05-15 17:49:13 -03:00
override fun requestShutdown ( requestId : Int , destNum : Int ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket ( id = requestId ) {
2022-11-29 17:47:49 -03:00
shutdownSeconds = 5
} )
2022-09-30 15:57:04 -03:00
}
2023-05-15 17:49:13 -03:00
override fun requestReboot ( requestId : Int , destNum : Int ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket ( id = requestId ) {
2022-11-29 17:47:49 -03:00
rebootSeconds = 5
} )
2022-06-06 17:29:09 -03:00
}
2023-05-15 17:49:13 -03:00
override fun requestFactoryReset ( requestId : Int , destNum : Int ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket ( id = requestId ) {
2022-11-29 17:47:49 -03:00
factoryReset = 1
} )
2022-06-06 17:29:09 -03:00
}
2022-09-18 18:35:13 -03:00
2023-05-15 17:49:13 -03:00
override fun requestNodedbReset ( requestId : Int , destNum : Int ) = toRemoteExceptions {
sendToRadio ( newMeshPacketTo ( destNum ) . buildAdminPacket ( id = requestId ) {
2022-11-29 17:47:49 -03:00
nodedbReset = 1
} )
2022-09-18 18:35:13 -03:00
}
2020-01-22 21:25:31 -08:00
}
2020-05-30 15:48:50 -07:00
}
2020-07-23 23:12:01 -04:00
2020-10-01 22:20:19 +02:00
fun updateNodeInfoTime ( it : NodeInfo , rxTime : Int ) {
2021-03-27 10:53:39 +08:00
it . lastHeard = rxTime
2020-10-01 22:20:19 +02:00
}