Merge pull request from mcumings/de-radio-service

`RadioInterfaceService` is no longer an Android `Service`
pull/429/head
Andre Kirchhoff 2022-05-06 23:29:18 -03:00 zatwierdzone przez GitHub
commit 5fb2be0591
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
11 zmienionych plików z 160 dodań i 245 usunięć

Wyświetl plik

@ -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"

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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()

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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) }
}
}

Wyświetl plik

@ -0,0 +1,6 @@
package com.geeksville.mesh.repository.radio
data class RadioServiceConnectionState(
val isConnected: Boolean = false,
val isPermanent: Boolean = false
)

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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()
}