Thursday, July 11, 2024
HomeiOS Developmentjavascript - How do I make a BLE connection to a peripheral...

javascript – How do I make a BLE connection to a peripheral with Kotlin Multiplatform or native Android?


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.



Supply hyperlink

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments