kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
Merge pull request #420 from mcumings/de-radio-service
`RadioInterfaceService` is no longer an Android `Service`pull/429/head
commit
5fb2be0591
app/src/main
aidl/com/geeksville/mesh
java/com/geeksville/mesh
|
@ -115,12 +115,6 @@
|
|||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!-- This is a private service which just does direct communication to the radio -->
|
||||
<service
|
||||
android:name="com.geeksville.mesh.repository.radio.RadioInterfaceService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- zxing for QR Code scanning: lock portrait orientation -->
|
||||
<activity
|
||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
// IRadioInterfaceService.aidl
|
||||
package com.geeksville.mesh;
|
||||
|
||||
// Declare any non-default types here with import statements
|
||||
|
||||
interface IRadioInterfaceService {
|
||||
|
||||
/** If the service is not currently connected to the radio, try to connect now. At boot the radio interface service will
|
||||
* not connect to a radio until this call is received. */
|
||||
void connect();
|
||||
|
||||
void sendToRadio(in byte [] a);
|
||||
|
||||
/// If a macaddress we will try to talk to our device, if null we will be idle.
|
||||
/// Any current connection will be dropped (even if the device address is the same) before reconnecting.
|
||||
/// Users should not call this directly, called only by MeshService
|
||||
/// Returns true if the device address actually changed, or false if no change was needed
|
||||
boolean setDeviceAddress(String deviceAddr);
|
||||
}
|
|
@ -80,15 +80,19 @@ A variable keepAllPackets, if set to true will suppress this behavior and instea
|
|||
* Note - this class intentionally dumb. It doesn't understand protobuf framing etc...
|
||||
* It is designed to be simple so it can be stubbed out with a simulated version as needed.
|
||||
*/
|
||||
class BluetoothInterface(val service: RadioInterfaceService, val address: String) : IRadioInterface,
|
||||
class BluetoothInterface(
|
||||
val context: Context,
|
||||
val service: RadioInterfaceService,
|
||||
val address: String) : IRadioInterface,
|
||||
Logging {
|
||||
|
||||
companion object : Logging, InterfaceFactory('x') {
|
||||
override fun createInterface(
|
||||
context: Context,
|
||||
service: RadioInterfaceService,
|
||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
||||
rest: String
|
||||
): IRadioInterface = BluetoothInterface(service, rest)
|
||||
): IRadioInterface = BluetoothInterface(context, service, rest)
|
||||
|
||||
init {
|
||||
registerFactory()
|
||||
|
@ -163,12 +167,12 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String
|
|||
init {
|
||||
// Note: this call does no comms, it just creates the device object (even if the
|
||||
// device is off/not connected)
|
||||
val device = getBluetoothAdapter(service)?.getRemoteDevice(address)
|
||||
val device = getBluetoothAdapter(context)?.getRemoteDevice(address)
|
||||
if (device != null) {
|
||||
info("Creating radio interface service. device=${address.anonymize}")
|
||||
|
||||
// Note this constructor also does no comm
|
||||
val s = SafeBluetooth(service, device)
|
||||
val s = SafeBluetooth(context, device)
|
||||
safe = s
|
||||
|
||||
startConnect()
|
||||
|
|
|
@ -17,7 +17,7 @@ abstract class InterfaceFactory(val prefix: Char) {
|
|||
factories[prefix] = this
|
||||
}
|
||||
|
||||
abstract fun createInterface(service: RadioInterfaceService, usbRepository: UsbRepository, rest: String): IRadioInterface
|
||||
abstract fun createInterface(context: Context, service: RadioInterfaceService, usbRepository: UsbRepository, rest: String): IRadioInterface
|
||||
|
||||
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
||||
open fun addressValid(context: Context, usbRepository: UsbRepository, rest: String): Boolean = true
|
||||
|
|
|
@ -14,6 +14,7 @@ import okhttp3.internal.toHexString
|
|||
class MockInterface(private val service: RadioInterfaceService) : Logging, IRadioInterface {
|
||||
companion object : Logging, InterfaceFactory('m') {
|
||||
override fun createInterface(
|
||||
context: Context,
|
||||
service: RadioInterfaceService,
|
||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
||||
rest: String
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
import android.content.Context
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
|
||||
class NopInterface : IRadioInterface {
|
||||
companion object : Logging, InterfaceFactory('n') {
|
||||
override fun createInterface(
|
||||
context: Context,
|
||||
service: RadioInterfaceService,
|
||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
||||
rest: String
|
||||
|
|
|
@ -1,31 +1,30 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Service
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.IBinder
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ServiceLifecycleDispatcher
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import com.geeksville.android.BinaryLogFile
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.IRadioInterfaceService
|
||||
import com.geeksville.mesh.CoroutineDispatchers
|
||||
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
import com.geeksville.mesh.service.EXTRA_CONNECTED
|
||||
import com.geeksville.mesh.service.EXTRA_PAYLOAD
|
||||
import com.geeksville.mesh.service.EXTRA_PERMANENT
|
||||
import com.geeksville.mesh.service.prefix
|
||||
import com.geeksville.util.anonymize
|
||||
import com.geeksville.util.ignoreException
|
||||
import com.geeksville.util.toRemoteExceptions
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
|
@ -38,21 +37,53 @@ import javax.inject.Inject
|
|||
* Note - this class intentionally dumb. It doesn't understand protobuf framing etc...
|
||||
* It is designed to be simple so it can be stubbed out with a simulated version as needed.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class RadioInterfaceService : Service(), Logging {
|
||||
class RadioInterfaceService @Inject constructor(
|
||||
private val context: Application,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val bluetoothRepository: BluetoothRepository,
|
||||
private val processLifecycle: Lifecycle,
|
||||
private val usbRepository: UsbRepository
|
||||
): Logging {
|
||||
|
||||
// The following is due to the fact that AIDL prevents us from extending from `LifecycleService`:
|
||||
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleDispatcher.lifecycle }
|
||||
private val lifecycleDispatcher: ServiceLifecycleDispatcher by lazy {
|
||||
ServiceLifecycleDispatcher(lifecycleOwner)
|
||||
private val _connectionState = MutableStateFlow(RadioServiceConnectionState())
|
||||
val connectionState = _connectionState.asStateFlow()
|
||||
|
||||
private val _receivedData = MutableSharedFlow<ByteArray>()
|
||||
val receivedData: SharedFlow<ByteArray> = _receivedData
|
||||
|
||||
private val logSends = false
|
||||
private val logReceives = false
|
||||
private lateinit var sentPacketsLog: BinaryLogFile // inited in onCreate
|
||||
private lateinit var receivedPacketsLog: BinaryLogFile
|
||||
|
||||
/**
|
||||
* We recreate this scope each time we stop an interface
|
||||
*/
|
||||
var serviceScope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
private var radioIf: IRadioInterface = NopInterface()
|
||||
|
||||
/** true if we have started our interface
|
||||
*
|
||||
* Note: an interface may be started without necessarily yet having a connection
|
||||
*/
|
||||
private var isStarted = false
|
||||
|
||||
/// true if our interface is currently connected to a device
|
||||
private var isConnected = false
|
||||
|
||||
init {
|
||||
processLifecycle.coroutineScope.launch {
|
||||
bluetoothRepository.state.collect { state ->
|
||||
if (state.enabled) {
|
||||
startInterface()
|
||||
} else {
|
||||
stopInterface()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var bluetoothRepository: BluetoothRepository
|
||||
|
||||
@Inject
|
||||
lateinit var usbRepository: UsbRepository
|
||||
|
||||
companion object : Logging {
|
||||
/**
|
||||
* The RECEIVED_FROMRADIO
|
||||
|
@ -133,38 +164,16 @@ class RadioInterfaceService : Service(), Logging {
|
|||
}
|
||||
return address
|
||||
}
|
||||
|
||||
/// If our service is currently running, this pointer can be used to reach it (in case setBondedDeviceAddress is called)
|
||||
private var runningService: RadioInterfaceService? = null
|
||||
}
|
||||
|
||||
private val logSends = false
|
||||
private val logReceives = false
|
||||
private lateinit var sentPacketsLog: BinaryLogFile // inited in onCreate
|
||||
private lateinit var receivedPacketsLog: BinaryLogFile
|
||||
|
||||
/**
|
||||
* We recreate this scope each time we stop an interface
|
||||
*/
|
||||
var serviceScope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
private var radioIf: IRadioInterface = NopInterface()
|
||||
|
||||
/** true if we have started our interface
|
||||
*
|
||||
* Note: an interface may be started without necessarily yet having a connection
|
||||
*/
|
||||
private var isStarted = false
|
||||
|
||||
/// true if our interface is currently connected to a device
|
||||
private var isConnected = false
|
||||
|
||||
private fun broadcastConnectionChanged(isConnected: Boolean, isPermanent: Boolean) {
|
||||
debug("Broadcasting connection=$isConnected")
|
||||
val intent = Intent(RADIO_CONNECTED_ACTION)
|
||||
intent.putExtra(EXTRA_CONNECTED, isConnected)
|
||||
intent.putExtra(EXTRA_PERMANENT, isPermanent)
|
||||
sendBroadcast(intent)
|
||||
|
||||
processLifecycle.coroutineScope.launch(dispatchers.default) {
|
||||
_connectionState.emit(
|
||||
RadioServiceConnectionState(isConnected, isPermanent)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a packet/command out the radio link, this routine can block if it needs to
|
||||
|
@ -181,10 +190,9 @@ class RadioInterfaceService : Service(), Logging {
|
|||
|
||||
// ignoreException { debug("FromRadio: ${MeshProtos.FromRadio.parseFrom(p)}") }
|
||||
|
||||
broadcastReceivedFromRadio(
|
||||
this,
|
||||
p
|
||||
)
|
||||
processLifecycle.coroutineScope.launch(dispatchers.io) {
|
||||
_receivedData.emit(p)
|
||||
}
|
||||
}
|
||||
|
||||
fun onConnect() {
|
||||
|
@ -201,48 +209,12 @@ class RadioInterfaceService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
runningService = this
|
||||
lifecycleDispatcher.onServicePreSuperOnCreate()
|
||||
super.onCreate()
|
||||
|
||||
lifecycleOwner.lifecycle.coroutineScope.launch {
|
||||
bluetoothRepository.state.collect { state ->
|
||||
if (state.enabled) {
|
||||
startInterface()
|
||||
} else {
|
||||
stopInterface()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
lifecycleDispatcher.onServicePreSuperOnStart()
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
stopInterface()
|
||||
serviceScope.cancel("Destroying RadioInterface")
|
||||
runningService = null
|
||||
lifecycleDispatcher.onServicePreSuperOnDestroy()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
lifecycleDispatcher.onServicePreSuperOnBind()
|
||||
return binder
|
||||
}
|
||||
|
||||
|
||||
/** Start our configured interface (if it isn't already running) */
|
||||
private fun startInterface() {
|
||||
if (radioIf !is NopInterface)
|
||||
warn("Can't start interface - $radioIf is already running")
|
||||
else {
|
||||
val address = getBondedDeviceAddress(this, usbRepository)
|
||||
val address = getBondedDeviceAddress(context, usbRepository)
|
||||
if (address == null)
|
||||
warn("No bonded mesh radio, can't start interface")
|
||||
else {
|
||||
|
@ -250,14 +222,14 @@ class RadioInterfaceService : Service(), Logging {
|
|||
isStarted = true
|
||||
|
||||
if (logSends)
|
||||
sentPacketsLog = BinaryLogFile(this, "sent_log.pb")
|
||||
sentPacketsLog = BinaryLogFile(context, "sent_log.pb")
|
||||
if (logReceives)
|
||||
receivedPacketsLog = BinaryLogFile(this, "receive_log.pb")
|
||||
receivedPacketsLog = BinaryLogFile(context, "receive_log.pb")
|
||||
|
||||
val c = address[0]
|
||||
val rest = address.substring(1)
|
||||
radioIf =
|
||||
InterfaceFactory.getFactory(c)?.createInterface(this, usbRepository, rest) ?: NopInterface()
|
||||
InterfaceFactory.getFactory(c)?.createInterface(context, this, usbRepository, rest) ?: NopInterface()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -291,7 +263,7 @@ class RadioInterfaceService : Service(), Logging {
|
|||
*/
|
||||
@SuppressLint("NewApi")
|
||||
private fun setBondedDeviceAddress(address: String?): Boolean {
|
||||
return if (getBondedDeviceAddress(this, usbRepository) == address && isStarted) {
|
||||
return if (getBondedDeviceAddress(context, usbRepository) == address && isStarted) {
|
||||
warn("Ignoring setBondedDevice ${address.anonymize}, because we are already using that device")
|
||||
false
|
||||
} else {
|
||||
|
@ -309,7 +281,7 @@ class RadioInterfaceService : Service(), Logging {
|
|||
|
||||
debug("Setting bonded device to ${address.anonymize}")
|
||||
|
||||
getPrefs(this).edit(commit = true) {
|
||||
getPrefs(context).edit(commit = true) {
|
||||
if (address == null)
|
||||
this.remove(DEVADDR_KEY)
|
||||
else
|
||||
|
@ -322,23 +294,20 @@ class RadioInterfaceService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
private val binder = object : IRadioInterfaceService.Stub() {
|
||||
fun setDeviceAddress(deviceAddr: String?): Boolean = toRemoteExceptions {
|
||||
setBondedDeviceAddress(deviceAddr)
|
||||
}
|
||||
|
||||
override fun setDeviceAddress(deviceAddr: String?): Boolean = toRemoteExceptions {
|
||||
setBondedDeviceAddress(deviceAddr)
|
||||
}
|
||||
/** If the service is not currently connected to the radio, try to connect now. At boot the radio interface service will
|
||||
* not connect to a radio until this call is received. */
|
||||
fun connect() = toRemoteExceptions {
|
||||
// We don't start actually talking to our device until MeshService binds to us - this prevents
|
||||
// broadcasting connection events before MeshService is ready to receive them
|
||||
startInterface()
|
||||
}
|
||||
|
||||
/** If the service is not currently connected to the radio, try to connect now. At boot the radio interface service will
|
||||
* not connect to a radio until this call is received. */
|
||||
override fun connect() = toRemoteExceptions {
|
||||
// We don't start actually talking to our device until MeshService binds to us - this prevents
|
||||
// broadcasting connection events before MeshService is ready to receive them
|
||||
startInterface()
|
||||
}
|
||||
|
||||
override fun sendToRadio(a: ByteArray) {
|
||||
// Do this in the IO thread because it might take a while (and we don't care about the result code)
|
||||
serviceScope.handledLaunch { handleSendToRadio(a) }
|
||||
}
|
||||
fun sendToRadio(a: ByteArray) {
|
||||
// Do this in the IO thread because it might take a while (and we don't care about the result code)
|
||||
serviceScope.handledLaunch { handleSendToRadio(a) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
data class RadioServiceConnectionState(
|
||||
val isConnected: Boolean = false,
|
||||
val isPermanent: Boolean = false
|
||||
)
|
|
@ -19,6 +19,7 @@ class SerialInterface(
|
|||
StreamInterface(service), Logging {
|
||||
companion object : Logging, InterfaceFactory('s') {
|
||||
override fun createInterface(
|
||||
context: Context,
|
||||
service: RadioInterfaceService,
|
||||
usbRepository: UsbRepository,
|
||||
rest: String
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
import android.content.Context
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
import com.geeksville.util.Exceptions
|
||||
|
@ -18,6 +19,7 @@ class TCPInterface(service: RadioInterfaceService, private val address: String)
|
|||
|
||||
companion object : Logging, InterfaceFactory('t') {
|
||||
override fun createInterface(
|
||||
context: Context,
|
||||
service: RadioInterfaceService,
|
||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
||||
rest: String
|
||||
|
|
|
@ -2,10 +2,8 @@ package com.geeksville.mesh.service
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Service
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.os.RemoteException
|
||||
|
@ -15,7 +13,6 @@ import androidx.core.content.edit
|
|||
import com.geeksville.analytics.DataPair
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.android.ServiceClient
|
||||
import com.geeksville.android.isGooglePlayAvailable
|
||||
import com.geeksville.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.*
|
||||
|
@ -27,6 +24,7 @@ import com.geeksville.mesh.database.entity.Packet
|
|||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.repository.radio.BluetoothInterface
|
||||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||
import com.geeksville.mesh.repository.radio.RadioServiceConnectionState
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
import com.geeksville.mesh.service.SoftwareUpdateService.Companion.ProgressNotStarted
|
||||
import com.geeksville.util.*
|
||||
|
@ -56,9 +54,15 @@ import kotlin.math.max
|
|||
*/
|
||||
@AndroidEntryPoint
|
||||
class MeshService : Service(), Logging {
|
||||
@Inject
|
||||
lateinit var dispatchers: CoroutineDispatchers
|
||||
|
||||
@Inject
|
||||
lateinit var packetRepository: Lazy<PacketRepository>
|
||||
|
||||
@Inject
|
||||
lateinit var radioInterfaceService: RadioInterfaceService
|
||||
|
||||
@Inject
|
||||
lateinit var usbRepository: Lazy<UsbRepository>
|
||||
|
||||
|
@ -135,13 +139,6 @@ class MeshService : Service(), Logging {
|
|||
// If we've ever read a valid region code from our device it will be here
|
||||
var curRegionValue = RadioConfigProtos.RegionCode.Unset_VALUE
|
||||
|
||||
val radio = ServiceClient {
|
||||
IRadioInterfaceService.Stub.asInterface(it).apply {
|
||||
// Now that we are connected to the radio service, tell it to connect to the radio
|
||||
connect()
|
||||
}
|
||||
}
|
||||
|
||||
private val locationCallback = MeshServiceLocationCallback(
|
||||
::sendPositionScoped,
|
||||
onSendPositionFailed = { onConnectionChanged(ConnectionState.DEVICE_SLEEP) },
|
||||
|
@ -269,16 +266,11 @@ class MeshService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
/// Safely access the radio service, if not connected an exception will be thrown
|
||||
private val connectedRadio: IRadioInterfaceService
|
||||
get() = (if (connectionState == ConnectionState.CONNECTED) radio.serviceP else null)
|
||||
?: throw RadioNotConnectedException()
|
||||
|
||||
/** Send a command/packet to our radio. But cope with the possiblity that we might start up
|
||||
before we are fully bound to the RadioInterfaceService
|
||||
@param requireConnected set to false if you are okay with using a partially connected device (i.e. during startup)
|
||||
*/
|
||||
private fun sendToRadio(p: ToRadio.Builder, requireConnected: Boolean = true) {
|
||||
private fun sendToRadio(p: ToRadio.Builder) {
|
||||
val built = p.build()
|
||||
debug("Sending to radio ${built.toPIIString()}")
|
||||
val b = built.toByteArray()
|
||||
|
@ -286,21 +278,16 @@ class MeshService : Service(), Logging {
|
|||
if (SoftwareUpdateService.isUpdating)
|
||||
throw IsUpdatingException()
|
||||
|
||||
if (requireConnected)
|
||||
connectedRadio.sendToRadio(b)
|
||||
else {
|
||||
val s = radio.serviceP ?: throw RadioNotConnectedException()
|
||||
s.sendToRadio(b)
|
||||
}
|
||||
radioInterfaceService.sendToRadio(b)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a mesh packet to the radio, if the radio is not currently connected this function will throw NotConnectedException
|
||||
*/
|
||||
private fun sendToRadio(packet: MeshPacket, requireConnected: Boolean = true) {
|
||||
private fun sendToRadio(packet: MeshPacket) {
|
||||
sendToRadio(ToRadio.newBuilder().apply {
|
||||
this.packet = packet
|
||||
}, requireConnected)
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateMessageNotification(message: DataPacket) =
|
||||
|
@ -338,20 +325,17 @@ class MeshService : Service(), Logging {
|
|||
serviceScope.handledLaunch {
|
||||
loadSettings() // Load our last known node DB
|
||||
|
||||
// we listen for messages from the radio receiver _before_ trying to create the service
|
||||
val filter = IntentFilter().apply {
|
||||
addAction(RadioInterfaceService.RECEIVE_FROMRADIO_ACTION)
|
||||
addAction(RadioInterfaceService.RADIO_CONNECTED_ACTION)
|
||||
}
|
||||
registerReceiver(radioInterfaceReceiver, filter)
|
||||
|
||||
// We in turn need to use the radiointerface service
|
||||
val intent = Intent(this@MeshService, RadioInterfaceService::class.java)
|
||||
// intent.action = IMeshService::class.java.name
|
||||
radio.connect(this@MeshService, intent, Context.BIND_AUTO_CREATE)
|
||||
|
||||
// the rest of our init will happen once we are in radioConnection.onServiceConnected
|
||||
radioInterfaceService.connect()
|
||||
}
|
||||
serviceScope.handledLaunch {
|
||||
radioInterfaceService.connectionState.collect(::onRadioConnectionState)
|
||||
}
|
||||
serviceScope.handledLaunch {
|
||||
radioInterfaceService.receivedData.collect(::onReceiveFromRadio)
|
||||
}
|
||||
|
||||
// the rest of our init will happen once we are in radioConnection.onServiceConnected
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -375,12 +359,6 @@ class MeshService : Service(), Logging {
|
|||
override fun onDestroy() {
|
||||
info("Destroying mesh service")
|
||||
|
||||
// This might fail if we get destroyed before the handledLaunch completes
|
||||
ignoreException(silent = true) {
|
||||
unregisterReceiver(radioInterfaceReceiver)
|
||||
}
|
||||
|
||||
radio.close()
|
||||
saveSettings()
|
||||
|
||||
stopForeground(true) // Make sure we aren't using the notification first
|
||||
|
@ -1217,67 +1195,44 @@ class MeshService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives messages from our BT radio service and processes them to update our model
|
||||
* and send to clients as needed.
|
||||
*/
|
||||
private val radioInterfaceReceiver = object : BroadcastReceiver() {
|
||||
|
||||
// Important to never throw exceptions out of onReceive
|
||||
override fun onReceive(context: Context, intent: Intent) = exceptionReporter {
|
||||
// NOTE: Do not call handledLaunch here, because it can cause out of order message processing - because each routine is scheduled independently
|
||||
// serviceScope.handledLaunch {
|
||||
debug("Received broadcast ${intent.action}")
|
||||
when (intent.action) {
|
||||
RadioInterfaceService.RADIO_CONNECTED_ACTION -> {
|
||||
try {
|
||||
// sleep now disabled by default on ESP32, permanent is true unless isPowerSaving enabled
|
||||
val lsEnabled = radioConfig?.preferences?.isPowerSaving ?: false
|
||||
val connected = intent.getBooleanExtra(EXTRA_CONNECTED, false)
|
||||
val permanent = intent.getBooleanExtra(EXTRA_PERMANENT, false) || !lsEnabled
|
||||
onConnectionChanged(
|
||||
when {
|
||||
connected -> ConnectionState.CONNECTED
|
||||
permanent -> ConnectionState.DISCONNECTED
|
||||
else -> ConnectionState.DEVICE_SLEEP
|
||||
}
|
||||
)
|
||||
} catch (ex: RemoteException) {
|
||||
// This can happen sometimes (especially if the device is slowly dying due to killing power, don't report to crashlytics
|
||||
warn("Abandoning reconnect attempt, due to errors during init: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
RadioInterfaceService.RECEIVE_FROMRADIO_ACTION -> {
|
||||
val bytes = intent.getByteArrayExtra(EXTRA_PAYLOAD)!!
|
||||
try {
|
||||
val proto =
|
||||
MeshProtos.FromRadio.parseFrom(bytes)
|
||||
// info("Received from radio service: ${proto.toOneLineString()}")
|
||||
when (proto.payloadVariantCase.number) {
|
||||
MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket(
|
||||
proto.packet
|
||||
)
|
||||
|
||||
MeshProtos.FromRadio.CONFIG_COMPLETE_ID_FIELD_NUMBER -> handleConfigComplete(
|
||||
proto.configCompleteId
|
||||
)
|
||||
|
||||
MeshProtos.FromRadio.MY_INFO_FIELD_NUMBER -> handleMyInfo(proto.myInfo)
|
||||
|
||||
MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> handleNodeInfo(proto.nodeInfo)
|
||||
|
||||
// MeshProtos.FromRadio.RADIO_FIELD_NUMBER -> handleRadioConfig(proto.radio)
|
||||
|
||||
else -> errormsg("Unexpected FromRadio variant")
|
||||
}
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
errormsg("Invalid Protobuf from radio, len=${bytes.size}", ex)
|
||||
}
|
||||
}
|
||||
|
||||
else -> errormsg("Unexpected radio interface broadcast")
|
||||
private fun onRadioConnectionState(state: RadioServiceConnectionState) {
|
||||
// sleep now disabled by default on ESP32, permanent is true unless isPowerSaving enabled
|
||||
val lsEnabled = radioConfig?.preferences?.isPowerSaving ?: false
|
||||
val connected = state.isConnected
|
||||
val permanent = state.isPermanent || !lsEnabled
|
||||
onConnectionChanged(
|
||||
when {
|
||||
connected -> ConnectionState.CONNECTED
|
||||
permanent -> ConnectionState.DISCONNECTED
|
||||
else -> ConnectionState.DEVICE_SLEEP
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun onReceiveFromRadio(bytes: ByteArray) {
|
||||
try {
|
||||
val proto =
|
||||
MeshProtos.FromRadio.parseFrom(bytes)
|
||||
// info("Received from radio service: ${proto.toOneLineString()}")
|
||||
when (proto.payloadVariantCase.number) {
|
||||
MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket(
|
||||
proto.packet
|
||||
)
|
||||
|
||||
MeshProtos.FromRadio.CONFIG_COMPLETE_ID_FIELD_NUMBER -> handleConfigComplete(
|
||||
proto.configCompleteId
|
||||
)
|
||||
|
||||
MeshProtos.FromRadio.MY_INFO_FIELD_NUMBER -> handleMyInfo(proto.myInfo)
|
||||
|
||||
MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> handleNodeInfo(proto.nodeInfo)
|
||||
|
||||
// MeshProtos.FromRadio.RADIO_FIELD_NUMBER -> handleRadioConfig(proto.radio)
|
||||
|
||||
else -> errormsg("Unexpected FromRadio variant")
|
||||
}
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
errormsg("Invalid Protobuf from radio, len=${bytes.size}", ex)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1533,13 +1488,13 @@ class MeshService : Service(), Logging {
|
|||
private fun requestRadioConfig() {
|
||||
sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket(wantResponse = true) {
|
||||
getRadioRequest = true
|
||||
}, requireConnected = false)
|
||||
})
|
||||
}
|
||||
|
||||
private fun requestChannel(channelIndex: Int) {
|
||||
sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket(wantResponse = true) {
|
||||
getChannelRequest = channelIndex + 1
|
||||
}, requireConnected = false)
|
||||
})
|
||||
}
|
||||
|
||||
private fun setChannel(channel: ChannelProtos.Channel) {
|
||||
|
@ -1759,7 +1714,7 @@ class MeshService : Service(), Logging {
|
|||
override fun setDeviceAddress(deviceAddr: String?) = toRemoteExceptions {
|
||||
debug("Passing through device change to radio service: ${deviceAddr.anonymize}")
|
||||
|
||||
val res = radio.service.setDeviceAddress(deviceAddr)
|
||||
val res = radioInterfaceService.setDeviceAddress(deviceAddr)
|
||||
if (res) {
|
||||
discardNodeDB()
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue