For BLE peripheral connections i am utilizing Kable library, which helps Android, iOS/macOS and Javascript targets. With my reply it is possible for you to to implement your class to hook up with BLE peripherals in lower than hour. Pure magic!
This library has an amazing non prolonged documentation, I like to recommend studying it to know the options of BLE on completely different methods earlier than viewing my reply.
At the start, I described the doable connection states. They’ve been divided into three differing types: the inner implementation of my predominant Base class will work with Usable
varieties, the library will move issues from the system utilizing Unusable
varieties, and the person interface may also have a NoPermissions
sort obtainable to indicate correct state in a single place.
// bundle core.mannequin
sealed interface BluetoothConnectionStatus {
/**
* Solely on Android and iOS. It's implied that you'll use this within the UI layer:
*
* ```
* val correctConnectionStatus =
* if (btPermissions.allPermissionsGranted) state.connectionStatus else BluetoothConnectionStatus.NoPermissions
* ```
*/
information object NoPermissions : BluetoothConnectionStatus
enum class Unusable : BluetoothConnectionStatus {
/** Bluetooth not obtainable. */
UNAVAILABLE,
/**
* Solely on Android 11 and under (Bluetooth is not going to work when GPS is switched off).
*
* To allow, allow it by way of statusbar, use the Google Providers API or go to location settings:
*
* ```
* enjoyable Context.goToLocationSettings() = startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
* ```
*/
LOCATION_SHOULD_BE_ENABLED,
/**
* Solely on Android 11 and decrease.
*
* To allow (on Android), use this:
*
* ```
* enjoyable Context.isPermissionProvided(permission: String): Boolean =
* ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
*
* @SuppressLint("MissingPermission")
* enjoyable Context.enableBluetoothDialog() {
* if (Construct.VERSION.SDK_INT >= Construct.VERSION_CODES.S &&
* !isPermissionProvided(Manifest.permission.BLUETOOTH_CONNECT)
* ) {
* return
* }
*
* startActivity(Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE))
* }
* ```
*/
DISABLED,
}
enum class Usable : BluetoothConnectionStatus {
ENABLED,
SCANNING,
CONNECTING,
CONNECTED,
}
}
Subsequent, let’s transfer on to the primary class that might be used to explain your machine via inheritance. It offers you the power to attach and work with a single machine, and if you wish to connect with a number of gadgets or several types of gadgets, you have to a number of objects. In the usual case of connecting to just one machine, you may get by with only one singleton.
// bundle core.ble.base
import com.juul.kable.Attribute
import com.juul.kable.ObsoleteKableApi
import com.juul.kable.Peripheral
import com.juul.kable.PlatformAdvertisement
import com.juul.kable.ServicesDiscoveredPeripheral
import com.juul.kable.State
import com.juul.kable.WriteType
import com.juul.kable.characteristicOf
import com.juul.kable.logs.Logging
import core.mannequin.BluetoothConnectionStatus
import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.movement.Movement
import kotlinx.coroutines.movement.MutableStateFlow
import kotlinx.coroutines.movement.mix
import kotlinx.coroutines.movement.first
import kotlinx.coroutines.launch
import kotlin.coroutines.cancellation.CancellationException
import kotlin.coroutines.coroutineContext
// base UUID for predefined traits: 0000****-0000-1000-8000-00805f9b34fb
non-public const val RETRY_ATTEMPTS = 7
/** Used to rapidly create lessons to hook up with BLE peripherals. */
summary class BaseBleDevice(
non-public val serviceUuid: String,
non-public val platformBluetoothManager: PlatformBluetoothManager,
non-public val useKableLogging: Boolean = false,
) {
non-public var connectedPeripheral: Peripheral? = null
non-public val _connectionStatus: MutableStateFlow<BluetoothConnectionStatus.Usable> =
MutableStateFlow(BluetoothConnectionStatus.Usable.ENABLED)
/** Gives present connection standing. */
val connectionStatus: Movement<BluetoothConnectionStatus> = mix(
platformBluetoothManager.systemBluetoothProblemStatus,
_connectionStatus,
) { problemStatusOrNull, internalStatus -> problemStatusOrNull ?: internalStatus }
/** Used to construct public `join` operate. */
protected droop enjoyable scanAndConnect(
observeList: Record<Pair<String, droop (ByteArray) -> Unit>> = emptyList(),
onServicesDiscovered: droop ServicesDiscoveredPeripheral.() -> Unit = {},
onSuccessfulConnect: droop () -> Unit = {},
advertisementFilter: droop PlatformAdvertisement.() -> Boolean = { true },
makes an attempt: Int = RETRY_ATTEMPTS,
): Boolean {
if (!platformBluetoothManager.isPermissionsProvided ||
connectionStatus.first() != BluetoothConnectionStatus.Usable.ENABLED ||
makes an attempt < 1
) {
return false
}
val coroutineScope = CoroutineScope(coroutineContext)
val peripheral = attempt {
_connectionStatus.worth = BluetoothConnectionStatus.Usable.SCANNING
platformBluetoothManager.getFirstPeripheral(
coroutineScope = coroutineScope,
serviceUuid = serviceUuid,
advertisementFilter = advertisementFilter,
) {
if (useKableLogging) {
logging {
@OptIn(ObsoleteKableApi::class)
information = Logging.DataProcessor { information, _, _, _, _ ->
information.joinToString { byte -> byte.toString() }
}
degree = Logging.Degree.Information // Information > Occasions > Warnings
}
}
onServicesDiscovered(motion = onServicesDiscovered)
}.additionally { coroutineScope.setupPeripheral(it, observeList) }
} catch (e: Throwable) {
Napier.e("scope.peripheral() exception caught: $e")
_connectionStatus.worth = BluetoothConnectionStatus.Usable.ENABLED
coroutineContext.ensureActive()
return if (e !is UnsupportedOperationException && e !is CancellationException && makes an attempt - 1 > 0) {
Napier.w("Retrying...")
scanAndConnect(
observeList = observeList,
onServicesDiscovered = onServicesDiscovered,
onSuccessfulConnect = onSuccessfulConnect,
advertisementFilter = advertisementFilter,
makes an attempt = makes an attempt - 1,
)
} else {
false
}
}
return connectToPeripheral(
peripheral = peripheral,
makes an attempt = makes an attempt,
onSuccessfulConnect = onSuccessfulConnect,
)
}
/** Used to construct public `reconnect` operate.
*
* Use it when you want quick reconnect, which might be cancelled in few seconds if peripheral will not discovered. */
protected droop enjoyable reconnect(onSuccessfulConnect: droop () -> Unit = {}): Boolean {
if (connectionStatus.first() is BluetoothConnectionStatus.Unusable) {
return false
}
return connectedPeripheral?.let {
connectToPeripheral(
peripheral = it,
makes an attempt = RETRY_ATTEMPTS,
onSuccessfulConnect = onSuccessfulConnect,
)
} ?: false
}
/** Name this operate to disconnect the energetic connection.
*
* To cancel in-flight connection makes an attempt it's best to cancel `Job` with working `join`.
*
* In case you are utilizing `Job` cancellation to disconnect the energetic connection you then will not
* have the ability to use `reconnect` as a result of `setupPeripheral` launches might be additionally cancelled.
*/
droop enjoyable disconnect() {
connectedPeripheral?.disconnect()
}
/**
* Can be utilized to create specified writing features to ship some values to the machine.
*
* Set **`waitForResponse`** to
* * **`false`** if attribute solely helps `PROPERTY_WRITE_NO_RESPONSE`;
* * **`true`** if attribute helps `PROPERTY_WRITE`.
*/
protected droop enjoyable writeTo(
characteristicUuid: String,
byteArray: ByteArray,
waitForResponse: Boolean,
makes an attempt: Int = RETRY_ATTEMPTS,
): Boolean {
if (connectionStatus.first() is BluetoothConnectionStatus.Unusable) {
return false
}
connectedPeripheral?.let { currentPeripheral ->
repeat(makes an attempt) {
attempt {
currentPeripheral.write(
attribute = characteristicOf(serviceUuid, characteristicUuid),
information = byteArray,
writeType = if (waitForResponse) WriteType.WithResponse else WriteType.WithoutResponse,
)
return true
} catch (e: Exception) {
Napier.e("writeTo exception caught: $e")
coroutineContext.ensureActive()
if (e is CancellationException) {
return false
} else if (it != makes an attempt - 1) {
Napier.w("Retrying...")
}
}
}
}
return false
}
/** Can be utilized to create specified studying features to get some values from the machine. */
protected droop enjoyable readFrom(
characteristicUuid: String,
makes an attempt: Int = RETRY_ATTEMPTS,
readFunc: droop (Attribute) -> ByteArray? = { connectedPeripheral?.learn(it) },
): ByteArray? {
if (connectionStatus.first() is BluetoothConnectionStatus.Unusable) {
return null
}
repeat(makes an attempt) {
attempt {
return readFunc(characteristicOf(serviceUuid, characteristicUuid))
} catch (e: Exception) {
Napier.e("readFrom exception caught: $e")
coroutineContext.ensureActive()
if (e is CancellationException) {
return null
} else if (it != makes an attempt - 1) {
Napier.w("Retrying...")
}
}
}
return null
}
/**
* Can be utilized to get some values from the machine on companies found.
*
* @see readFrom
*/
protected droop enjoyable ServicesDiscoveredPeripheral.readFrom(
characteristicUuid: String,
makes an attempt: Int = RETRY_ATTEMPTS,
): ByteArray? = readFrom(characteristicUuid, makes an attempt, ::learn)
non-public enjoyable CoroutineScope.setupPeripheral(
peripheral: Peripheral,
observeList: Record<Pair<String, droop (ByteArray) -> Unit>>,
) {
launch {
var isCurrentlyStarted = true
peripheral.state.acquire { currentState ->
if (currentState !is State.Disconnected || !isCurrentlyStarted) {
isCurrentlyStarted = false
_connectionStatus.worth = when (currentState) {
is State.Disconnected,
State.Disconnecting,
-> BluetoothConnectionStatus.Usable.ENABLED
State.Connecting.Bluetooth,
State.Connecting.Providers,
State.Connecting.Observes,
-> BluetoothConnectionStatus.Usable.CONNECTING
State.Related,
-> BluetoothConnectionStatus.Usable.CONNECTED // or set it in connectToPeripheral()
}
}
}
}
observeList.forEach { (attribute, observer) ->
launch {
peripheral.observe(
characteristicOf(service = serviceUuid, attribute = attribute),
).acquire { byteArray ->
observer(byteArray)
}
}
}
}
non-public droop enjoyable connectToPeripheral(
peripheral: Peripheral,
makes an attempt: Int,
onSuccessfulConnect: droop () -> Unit,
): Boolean {
attempt {
peripheral.join()
connectedPeripheral = peripheral
} catch (e: Exception) {
Napier.e("connectToPeripheral exception caught: $e")
peripheral.disconnect()
_connectionStatus.worth = BluetoothConnectionStatus.Usable.ENABLED
Napier.e("fsdkljdsfj ${connectionStatus.first()}")
coroutineContext.ensureActive()
return if (e !is CancellationException && makes an attempt - 1 > 0) {
Napier.w("Retrying...")
connectToPeripheral(
peripheral = peripheral,
makes an attempt = makes an attempt - 1,
onSuccessfulConnect = onSuccessfulConnect,
)
} else {
false
}
}
onSuccessfulConnect()
// _connectionStatus.worth = BluetoothConnectionStatus.Usable.CONNECTED
return true
}
}
BaseBleDevice
is dependent upon PlatformBluetoothManager
, which is a KMP layer for working with completely different methods. Count on/Precise lessons are described under. In case your challenge is Android-only, it’s worthwhile to copy solely androidMain class and take away precise
key phrases from it.
commonMain:
// bundle core.ble.base
import com.juul.kable.Peripheral
import com.juul.kable.PeripheralBuilder
import com.juul.kable.PlatformAdvertisement
import core.mannequin.BluetoothConnectionStatus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.movement.Movement
/** Gives system particular Bluetooth connectivity options for KMP targets. */
anticipate class PlatformBluetoothManager {
/**
* Gives movement with commercials that can be utilized to indicate gadgets, choose certainly one of them,
* get `UUID`/`MAC` and use it in `advertisementFilter`.
*
* Not obtainable in JS goal.
*/
droop enjoyable getAdvertisements(serviceUuid: String): Movement<PlatformAdvertisement>
/** Gives first discovered peripheral to attach. */
droop enjoyable getFirstPeripheral(
coroutineScope: CoroutineScope,
serviceUuid: String,
advertisementFilter: droop PlatformAdvertisement.() -> Boolean = { true }, // by default the primary machine discovered
peripheralBuilderAction: PeripheralBuilder.() -> Unit,
): Peripheral
/** Returns the Bluetooth standing of the system: `Unusable` subtypes or null if it is `Usable`. */
val systemBluetoothProblemStatus: Movement<BluetoothConnectionStatus.Unusable?>
/** Signifies whether or not it's doable to begin scanning and connection. */
val isPermissionsProvided: Boolean
}
androidMain:
// bundle core.ble.base
import android.Manifest
import android.content material.Context
import android.content material.pm.PackageManager
import android.os.Construct
import androidx.core.content material.ContextCompat
import com.benasher44.uuid.uuidFrom
import com.juul.kable.Bluetooth
import com.juul.kable.Filter
import com.juul.kable.Peripheral
import com.juul.kable.PeripheralBuilder
import com.juul.kable.PlatformAdvertisement
import com.juul.kable.Motive
import com.juul.kable.Scanner
import com.juul.kable.peripheral
import core.mannequin.BluetoothConnectionStatus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.movement.filter
import kotlinx.coroutines.movement.first
import kotlinx.coroutines.movement.map
precise class PlatformBluetoothManager(non-public val context: Context) {
precise droop enjoyable getAdvertisements(serviceUuid: String) =
Scanner { filters = listOf(Filter.Service(uuidFrom(serviceUuid))) }.commercials
precise droop enjoyable getFirstPeripheral(
coroutineScope: CoroutineScope,
serviceUuid: String,
advertisementFilter: droop PlatformAdvertisement.() -> Boolean,
peripheralBuilderAction: PeripheralBuilder.() -> Unit,
): Peripheral = coroutineScope.peripheral(
commercial = getAdvertisements(serviceUuid).filter(predicate = advertisementFilter).first(),
builderAction = peripheralBuilderAction,
)
precise val systemBluetoothProblemStatus = Bluetooth.availability.map {
when (it) {
Bluetooth.Availability.Accessible -> null
is Bluetooth.Availability.Unavailable -> when (it.purpose) {
Motive.Off -> BluetoothConnectionStatus.Unusable.DISABLED
Motive.LocationServicesDisabled -> BluetoothConnectionStatus.Unusable.LOCATION_SHOULD_BE_ENABLED
else -> BluetoothConnectionStatus.Unusable.UNAVAILABLE
}
}
}
precise val isPermissionsProvided
get() = if (Construct.VERSION.SDK_INT < Construct.VERSION_CODES.S) {
context.isPermissionProvided(Manifest.permission.ACCESS_FINE_LOCATION)
} else {
context.isPermissionProvided(Manifest.permission.BLUETOOTH_SCAN) &&
context.isPermissionProvided(Manifest.permission.BLUETOOTH_CONNECT)
}
}
non-public enjoyable Context.isPermissionProvided(permission: String): Boolean =
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
jsMain:
// bundle core.ble.base
import com.benasher44.uuid.uuidFrom
import com.juul.kable.Bluetooth
import com.juul.kable.Filter
import com.juul.kable.Choices
import com.juul.kable.Peripheral
import com.juul.kable.PeripheralBuilder
import com.juul.kable.PlatformAdvertisement
import com.juul.kable.requestPeripheral
import core.mannequin.BluetoothConnectionStatus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.await
import kotlinx.coroutines.movement.Movement
import kotlinx.coroutines.movement.map
precise class PlatformBluetoothManager {
precise droop enjoyable getAdvertisements(serviceUuid: String): Movement<PlatformAdvertisement> =
throw NotImplementedError("Net Bluetooth would not permit to find close by gadgets")
precise droop enjoyable getFirstPeripheral(
coroutineScope: CoroutineScope,
serviceUuid: String,
advertisementFilter: droop PlatformAdvertisement.() -> Boolean, // not used: in JS solely person can choose machine
peripheralBuilderAction: PeripheralBuilder.() -> Unit,
): Peripheral = coroutineScope.requestPeripheral(
choices = Choices(filters = listOf(Filter.Service(uuidFrom(serviceUuid)))),
builderAction = peripheralBuilderAction,
).then(
onFulfilled = { it },
onRejected = {
throw UnsupportedOperationException(
"Cannot present popup as a result of person hasn't interacted with web page or person has closed pairing popup",
)
},
).await()
precise val systemBluetoothProblemStatus = Bluetooth.availability.map {
when (it) {
is Bluetooth.Availability.Unavailable -> BluetoothConnectionStatus.Unusable.UNAVAILABLE
Bluetooth.Availability.Accessible -> null
}
}
precise val isPermissionsProvided = true
}
appleMain (since I haven’t got a Mac to compile and check, the category is just not totally carried out):
// bundle core.ble.base
import com.benasher44.uuid.uuidFrom
import com.juul.kable.Bluetooth
import com.juul.kable.Filter
import com.juul.kable.Peripheral
import com.juul.kable.PeripheralBuilder
import com.juul.kable.PlatformAdvertisement
import com.juul.kable.Motive
import com.juul.kable.Scanner
import com.juul.kable.peripheral
import core.mannequin.BluetoothConnectionStatus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.movement.filter
import kotlinx.coroutines.movement.first
import kotlinx.coroutines.movement.map
precise class PlatformBluetoothManager {
precise droop enjoyable getAdvertisements(serviceUuid: String) =
Scanner { filters = listOf(Filter.Service(uuidFrom(serviceUuid))) }.commercials
precise droop enjoyable getFirstPeripheral(
coroutineScope: CoroutineScope,
serviceUuid: String,
advertisementFilter: droop PlatformAdvertisement.() -> Boolean,
peripheralBuilderAction: PeripheralBuilder.() -> Unit,
): Peripheral = coroutineScope.peripheral(
commercial = getAdvertisements(serviceUuid).filter(predicate = advertisementFilter).first(),
builderAction = peripheralBuilderAction,
)
precise val systemBluetoothProblemStatus = Bluetooth.availability.map {
when (it) {
Bluetooth.Availability.Accessible -> null
is Bluetooth.Availability.Unavailable -> when (it.purpose) {
Motive.Off -> BluetoothConnectionStatus.Unusable.DISABLED
// Motive.Unauthorized -> BluetoothConnectionStatus.Unusable.UNAUTHORIZED // use it provided that wanted
else -> BluetoothConnectionStatus.Unusable.UNAVAILABLE
}
}
}
// I do not develop apps for Apple and haven't got the power to debug the code,
// so you will have so as to add the implementation your self.
precise val isPermissionsProvided: Boolean
get() = TODO()
}
That is the primary half that it’s worthwhile to copy to your self to rapidly implement communication with BLE gadgets.
Beneath you’ll be able to see an instance class to work with a customized RFID reader, which you should use for instance to create your personal class. New values are handed to the listeners by way of SharedFlow
and StateFlow
. On connection we initialise listening for attribute updates by way of observeList
, pressure a few of them to be learn instantly after connection in onServicesDiscovered
, and require to attach solely to the handed macAddress
by way of advertisementFilter
. As well as, the category permits you to get connectionStatus
because it inherits from BaseBleDevice
and permits you to move the time to the machine by way of sendTime()
. Please notice that this class makes use of Android platform particular features, is just not suitable with KMP and is offered for instance solely.
// bundle core.ble.laundry
import core.ble.base.BaseBleDevice
import core.ble.base.PlatformBluetoothManager
import kotlinx.coroutines.movement.MutableSharedFlow
import kotlinx.coroutines.movement.MutableStateFlow
import kotlinx.coroutines.movement.SharedFlow
import kotlinx.coroutines.movement.StateFlow
import kotlinx.datetime.Clock
import javax.inject.Inject
import javax.inject.Singleton
non-public const val SERVICE_UUID = "f71381b8-c439-4b29-8256-620efaef0b4e"
// right here you can even use Bluetooth.BaseUuid.plus(0x2a19).toString()
non-public const val BATTERY_LEVEL_UUID = "00002a19-0000-1000-8000-00805f9b34fb"
non-public const val BATTERY_IS_CHARGING_UUID = "1170f274-a09b-46b3-88c5-0e1c67037861"
non-public const val RFID_UUID = "3d58f98d-63f0-43d5-a7d4-54fa1ed824ba"
non-public const val TIME_UUID = "016c2726-b22a-4cdc-912b-f626d1e4051e"
@Singleton
class PersonProviderDevice @Inject constructor(
platformBluetoothManager: PlatformBluetoothManager,
) : BaseBleDevice(SERVICE_UUID, platformBluetoothManager) {
non-public val _providedRfid = MutableSharedFlow<Lengthy>()
val providedRfid: SharedFlow<Lengthy> = _providedRfid
non-public val _batteryState = MutableStateFlow(BatteryState())
val batteryState: StateFlow<BatteryState> = _batteryState
droop enjoyable join(macAddress: String?) = scanAndConnect(
observeList = listOf(
RFID_UUID to { bytes ->
bytes.toULong()?.let { _providedRfid.emit(it.toLong()) }
},
BATTERY_LEVEL_UUID to { bytes ->
_batteryState.worth = _batteryState.worth.copy(degree = bytes.toBatteryLevel())
},
BATTERY_IS_CHARGING_UUID to { bytes ->
_batteryState.worth = _batteryState.worth.copy(isCharging = bytes.toIsCharging())
},
),
onServicesDiscovered = {
// requestMtu(512)
val degree = this.readFrom(BATTERY_LEVEL_UUID)?.toBatteryLevel()
val isCharging = this.readFrom(BATTERY_IS_CHARGING_UUID)?.toIsCharging()
_batteryState.worth = BatteryState(degree, isCharging ?: false)
},
advertisementFilter = { macAddress?.let { this.tackle.lowercase() == it.lowercase() } ?: true },
)
droop enjoyable sendTime(): Boolean {
val byteArrayWithTime = Clock.System.now().toEpochMilliseconds().toString().toByteArray()
return writeTo(TIME_UUID, byteArrayWithTime, true)
}
non-public enjoyable ByteArray.toBatteryLevel() = this.first().toInt()
non-public enjoyable ByteArray.toIsCharging() = this.first().toInt() == 1
}
/**
* Gives ULong from ByteArray encoded with little endian
*/
enjoyable ByteArray.toULong(measurement: Int = 8): ULong? =
if (this.measurement != measurement) {
null
} else {
var end result: ULong = 0u
repeat(measurement) {
end result = end result or ((this[it].toULong() and 0xFFu) shl (it * 8))
}
end result
}
information class BatteryState(
val degree: Int? = null,
val isCharging: Boolean = false,
)
This class goals to work with just one machine at a time. It doesn’t present the likelihood to disconnect from the machine by way of the usual disconnect()
operate, however it’s doable by way of Job
cancellation. reconnect()
can also be unavailable as a result of it is required on very uncommon events.
To work with this class, I added the next code to my ViewModel
. It permits you to routinely disconnect from the machine when the appliance is minimised, routinely join when the appliance is energetic and it’s doable to attach. You’ll want to name onResume() and onStop() from the suitable features in your Exercise
.
non-public var isLaunchingConnectionJob = false
non-public var connection: Job? = null
non-public var isAppResumed: Boolean = false
init {
viewModelScope.launch {
rfidRepository.connectionStatus.acquire {
state = state.copy(connectionStatus = it)
// used to reconnect when machine was disconnected
connectIfResumedAndNotConnectedOrSendTime()
}
}
}
enjoyable onResume() {
isAppResumed = true
viewModelScope.launch {
connectIfResumedAndNotConnectedOrSendTime()
}
}
enjoyable onStop() {
isAppResumed = false
viewModelScope.launch {
delay(2000)
if (!isAppResumed) {
connection?.cancelAndJoin()
}
}
}
non-public droop enjoyable connectIfResumedAndNotConnectedOrSendTime() {
if (isLaunchingConnectionJob) {
return
}
isLaunchingConnectionJob = true
if (isAppResumed && rfidRepository.connectionStatus.first() == BluetoothConnectionStatus.Usable.ENABLED) {
connection?.cancelAndJoin()
connection = viewModelScope.launch {
isLaunchingConnectionJob = false
rfidRepository.join()
whereas (true) {
delay(1000)
val isWritten = rfidRepository.sendTime()
if (!isWritten) {
connection?.cancelAndJoin()
}
}
}
} else {
isLaunchingConnectionJob = false
}
}
So, to work with BLE gadgets, all it’s worthwhile to do is copy the BluetoothConnectionStatus
, BaseBleDevice
and PlatformBluetoothManager
lessons into your challenge, describe the traits of your machine your self by creating an heir of BaseBleDevice
and connect with the machine from the ViewModel
.