kotlin ble migration

This commit is contained in:
Vineyro 2024-06-13 14:06:48 +07:00
parent a059cacda9
commit f65023b4c7
6 changed files with 228 additions and 358 deletions

View File

@ -11,8 +11,6 @@ import kotlinx.coroutines.launch
import llc.arma.ble.app.ui.common.BaseViewModel
import llc.arma.ble.domain.usecase.ExportToXlsx
import llc.arma.ble.domain.usecase.GetBleAroundFlow
import llc.arma.ble.domain.usecase.GetConnectedBleDevices
import llc.arma.ble.domain.usecase.MeasureData
import javax.inject.Inject
@HiltViewModel

View File

@ -1,26 +1,17 @@
package llc.arma.ble.data
import android.Manifest
import android.app.Application
import android.bluetooth.*
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.pm.PackageManager
import android.os.Build
import android.os.SystemClock
import android.util.Log
import androidx.core.app.ActivityCompat
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState
@ -37,22 +28,13 @@ import llc.arma.ble.domain.usecase.FftFrequency
import llc.arma.ble.domain.usecase.FftViewMode
import no.nordicsemi.android.common.core.DataByteArray
import no.nordicsemi.android.kotlin.ble.client.main.callback.ClientBleGatt
import no.nordicsemi.android.kotlin.ble.core.scanner.BleNumOfMatches
import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanMode
import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanResult
import no.nordicsemi.android.kotlin.ble.core.scanner.BleScannerCallbackType
import no.nordicsemi.android.kotlin.ble.core.scanner.BleScannerMatchMode
import no.nordicsemi.android.kotlin.ble.core.scanner.BleScannerSettings
import no.nordicsemi.android.kotlin.ble.scanner.BleScanner
import no.nordicsemi.android.kotlin.ble.scanner.aggregator.BleScanResultAggregator
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.math.PI
import kotlin.math.atan
import kotlin.math.pow
import kotlin.math.sqrt
val FftFrequency.sendData: Byte
@ -728,6 +710,8 @@ class BleRepositoryImpl @Inject constructor(
frequency: FftFrequency
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {
//return readAccelerometerSpectre(serial, app, accelScale, accelMode, fftAxis, fftMode, frequency)
var gatt: BluetoothGatt? = null
return callbackFlow {
@ -769,7 +753,9 @@ class BleRepositoryImpl @Inject constructor(
serial: String
): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>> {
var gatt: BluetoothGatt? = null
return readThermometerHistory(serial, app)
/*var gatt: BluetoothGatt? = null
return callbackFlow {
@ -799,7 +785,7 @@ class BleRepositoryImpl @Inject constructor(
gatt?.close()
}
}
}*/
}
@ -1199,15 +1185,15 @@ class BleRepositoryImpl @Inject constructor(
when(accelMode){
ANGLE -> {
x = calculateZAngle(
x = calculateAngle(
data[2].toFloat(),
data[1].toFloat()
) * 180f / Math.PI.toFloat()
y = calculateZAngle(
y = calculateAngle(
data[2].toFloat(),
data[0].toFloat()
) * 180f / Math.PI.toFloat()
z = calculateZAngle(
z = calculateAngle(
data[0].toFloat(),
data[1].toFloat()
) * 180f / Math.PI.toFloat()
@ -1287,16 +1273,6 @@ class BleRepositoryImpl @Inject constructor(
}
fun calculateAngle(
targetAxis: Float,
firstAxis: Float,
secondAxis: Float
): Float {
return atan(targetAxis.div(sqrt(firstAxis.pow(2) + secondAxis.pow(2))))
}
public fun calculateZAngle(
x: Float,
y: Float
): Float {

View File

@ -170,15 +170,15 @@ fun readAccelerometerHistory(
resultTemperaturePackage.chunked(3).withIndex().map {
Ble.Accelerometer.HistoryPoint.Angle(
date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
x = calculateZAngle(
x = calculateAngle(
it.value[2],
it.value[1]
) * 180f / Math.PI.toFloat(),
y = calculateZAngle(
y = calculateAngle(
it.value[2],
it.value[0]
) * 180f / Math.PI.toFloat(),
z = calculateZAngle(
z = calculateAngle(
it.value[0],
it.value[1]
) * 180f / Math.PI.toFloat()

View File

@ -214,6 +214,8 @@ class ReadAccelerometerSpectreCallback(
status: Int
){
Log.d("value", value.joinToString(separator = ""))
if(status == BluetoothGatt.GATT_SUCCESS){
when(readProperty){
Property.DATA_SIZE -> {
@ -454,25 +456,24 @@ class ReadAccelerometerSpectreCallback(
}
/*
@OptIn(ExperimentalUnsignedTypes::class)
fun readAccelerometerSpectre(
address: String,
mode: AccelViewMode,
scale: AccelScale,
app: Application,
accelScale: AccelScale,
accelMode: AccelViewMode,
fftAxis: FftAxis,
fftMode: FftViewMode,
frequency: FftFrequency,
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {
return flow {
var lastMeasureSystemTime: Long? = null
var bleMeasureInterval: Long? = null
var bleRealTime: Long? = null
var bleLastMeasureTime: Long? = null
var initialValue: Long? = null
var frequencyInterval: Long? = null
val resultTemperaturePackage: MutableList<Float> = mutableListOf()
val result = mutableListOf<List<UByte>>()
val resultAccelerometerPackage: MutableList<Float> = mutableListOf()
var expectedDataSize: Int? = null
@ -484,15 +485,45 @@ fun readAccelerometerSpectre(
ClientBleGatt.connect(app, address, CoroutineScope(Dispatchers.Default))
val characteristic = connection.discoverServices()
.findService(serviceUUID)
?.findCharacteristic(accelerometerReadUUID)
?: throw IllegalStateException()
val historyCharacteristic = connection.discoverServices()
.findService(serviceUUID)
?.findCharacteristic(accelerometerHistoryReadUUID)
?: throw IllegalStateException()
if(characteristic != null) {
Log.d("notification", "-1")
Log.d("notification", "0")
characteristic.write(DataByteArray(byteArrayOf(
4,
accelMode.sendData,
accelScale.sendData,
fftMode.sendData,
fftAxis.sendData,
frequency.sendData,
2
)))
Log.d("notification", "1")
val notifications = characteristic.getNotifications()
notifications.collect {
characteristic.write(DataByteArray.from(2))
var value = characteristic.read().value
Log.d("notification", "0")
historyCharacteristic.write(DataByteArray.from(2))
var value = historyCharacteristic.read().value
Log.d("value", value.joinToString(separator = ""))
Log.d("value", value.get2byteUIntAt(0).toString())
if (value.contentEquals(byteArrayOf(0, 0))) {
@ -510,118 +541,74 @@ fun readAccelerometerSpectre(
addAll(value.toList())
}.toByteArray()
characteristic.write(DataByteArray(writeData))
value = characteristic.read().value
historyCharacteristic.write(DataByteArray(writeData))
value = historyCharacteristic.read().value
var nextPackageDataCount = value.get2byteUIntAt(2)
while (nextPackageDataCount.toInt() != 0) {
val temperatureDataArray = if (value[0] == 250.toByte()) {
val accelerometerDataArray = if (value[0] == 250.toByte()) {
bleMeasureInterval = value.get4byteUIntAt(4).toLong()
bleLastMeasureTime = value.get4byteUIntAt(8).toLong()
bleRealTime = value.get4byteUIntAt(12).toLong()
initialValue = value.get4byteUIntAt(8).toLong()
frequencyInterval = value.get4byteUIntAt(4).toLong()
lastMeasureSystemTime =
System.currentTimeMillis() - ((bleRealTime!! - bleLastMeasureTime!!) * 1_000)
value.toUByteArray().asList().subList(16, value.size)
value.asList().subList(16, value.size)
} else {
value.toUByteArray().asList().subList(4, value.size)
value.asList().subList(4, value.size)
}
result.add(value.toUByteArray().toList())
nextPackageDataCount = value.get2byteUIntAt(2)
resultTemperaturePackage.addAll(
temperatureDataArray.chunked(2).map {
it.toUByteArray().toByteArray().get2byteShortAt().toFloat()
resultAccelerometerPackage.addAll(
accelerometerDataArray.chunked(2).map {
it.toByteArray().get2byteShortAt().toFloat()
}.toMutableList()
)
Log.d(
"received data size",
(temperatureDataArray.chunked(2).size).toString()
)
Log.d("next data size", nextPackageDataCount.toString())
expectedDataSize = nextPackageDataCount.toInt() + resultAccelerometerPackage.size
expectedDataSize =
nextPackageDataCount.toInt() + resultTemperaturePackage.size
emit(Result.success(ProgressState.Progress(0f / expectedDataSize!!.toFloat())))
emit(Result.success(ProgressState.Progress(resultAccelerometerPackage.size.toFloat() / expectedDataSize!!.toFloat())))
emit(Result.success(ProgressState.Progress(0f / expectedDataSize.toFloat())))
emit(Result.success(ProgressState.Progress(resultTemperaturePackage.size.toFloat() / expectedDataSize.toFloat())))
characteristic.write(DataByteArray.from(5))
value = characteristic.read().value
historyCharacteristic.write(DataByteArray.from(5))
value = historyCharacteristic.read().value
}
emit(
Result.success(
ProgressState.Finished(
when (mode) {
AccelViewMode.ROTATIONS,
AccelViewMode.ACCELERATION,
AccelViewMode.PEAK_ACCELERATION,
AccelViewMode.RMS -> {
resultTemperaturePackage.chunked(3).withIndex().map {
Ble.Accelerometer.HistoryPoint.Angle(
date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
x = (it.value[0] * scale.k) / Short.MAX_VALUE,
y = (it.value[1] * scale.k) / Short.MAX_VALUE,
z = (it.value[2] * scale.k) / Short.MAX_VALUE
)
}
}
AccelViewMode.ANGLE -> {
resultTemperaturePackage.chunked(3).withIndex().map {
Ble.Accelerometer.HistoryPoint.Angle(
date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
x = calculateZAngle(
it.value[2],
it.value[1]
) * 180f / Math.PI.toFloat(),
y = calculateZAngle(
it.value[2],
it.value[0]
) * 180f / Math.PI.toFloat(),
z = calculateZAngle(
it.value[0],
it.value[1]
) * 180f / Math.PI.toFloat()
)
}
}
AccelViewMode.VIBRATION -> {
resultTemperaturePackage.withIndex().map {
Ble.Accelerometer.HistoryPoint.Vibration(
date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
value = (it.value * scale.k) / Short.MAX_VALUE
)
}
}
resultAccelerometerPackage.withIndex().map {
Ble.Accelerometer.MeasurePoint(
frequency = frequencyInterval!! * it.index + initialValue!!,
value = (it.value * accelScale.k) / Short.MAX_VALUE
)
}
)
)
)
characteristic.write(DataByteArray(byteArrayOf(
4,
accelMode.sendData,
accelScale.sendData,
fftMode.sendData,
fftAxis.sendData,
frequency.sendData,
2
)))
}
} else {
emit(Result.failure(BleException.UnexpectedResponse))
}
} catch (err: Throwable) {
err.printStackTrace()
emit(Result.failure(BleException.UnexpectedResponse))
}
@ -636,4 +623,4 @@ fun readAccelerometerSpectre(
}*/
}

View File

@ -9,10 +9,18 @@ import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState
import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.usecase.AccelScale
import llc.arma.ble.domain.usecase.AccelViewMode
import no.nordicsemi.android.common.core.DataByteArray
import no.nordicsemi.android.kotlin.ble.client.main.callback.ClientBleGatt
class ReadTemperatureHistoryCallback(
private val app: Application,
@ -319,4 +327,135 @@ class ReadTemperatureHistoryCallback(
}
}
@OptIn(ExperimentalUnsignedTypes::class)
fun readThermometerHistory(
address: String,
app: Application,
): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>> {
return flow {
var lastMeasureSystemTime: Long? = null
var bleMeasureInterval: Long? = null
var bleRealTime: Long? = null
var bleLastMeasureTime: Long? = null
val resultTemperaturePackage: MutableList<Float> = mutableListOf()
var expectedDataSize: Int? = null
if (app.checkPermission()) {
try {
val connection =
ClientBleGatt.connect(app, address, CoroutineScope(Dispatchers.Default))
val characteristic = connection.discoverServices()
.findService(serviceUUID)
?.findCharacteristic(temperatureHistoryReadUUID)
?: throw IllegalStateException()
characteristic.write(DataByteArray.from(2))
var value = characteristic.read().value
if (value.contentEquals(byteArrayOf(0, 0))) {
emit(Result.success(ProgressState.Finished(emptyList())))
} else {
Log.d("expected data size", value.get2byteUIntAt(0).toString())
val writeData = mutableListOf(
1.toByte(),
0.toByte(),
0.toByte()
).apply {
addAll(value.toList())
}.toByteArray()
characteristic.write(DataByteArray(writeData))
value = characteristic.read().value
var nextPackageDataCount = value.get2byteUIntAt(2)
while (nextPackageDataCount.toInt() != 0) {
val temperatureDataArray = if (value[0] == 250.toByte()) {
bleMeasureInterval = value.get4byteUIntAt(4).toLong()
bleLastMeasureTime = value.get4byteUIntAt(8).toLong()
bleRealTime = value.get4byteUIntAt(12).toLong()
lastMeasureSystemTime =
System.currentTimeMillis() - ((bleRealTime - bleLastMeasureTime) * 1_000)
value.toUByteArray().asList().subList(16, value.size)
} else {
value.toUByteArray().asList().subList(4, value.size)
}
nextPackageDataCount = value.get2byteUIntAt(2)
resultTemperaturePackage.addAll(
temperatureDataArray.chunked(2).map {
it.toUByteArray().toTemperature()
}.toMutableList()
)
Log.d(
"received data size",
(temperatureDataArray.chunked(2).size).toString()
)
Log.d("next data size", nextPackageDataCount.toString())
expectedDataSize =
nextPackageDataCount.toInt() + resultTemperaturePackage.size
emit(Result.success(ProgressState.Progress(0f / expectedDataSize.toFloat())))
emit(Result.success(ProgressState.Progress(resultTemperaturePackage.size.toFloat() / expectedDataSize.toFloat())))
characteristic.write(DataByteArray.from(5))
value = characteristic.read().value
}
emit(
Result.success(
ProgressState.Finished(
resultTemperaturePackage.withIndex().map {
Ble.Thermometer.MeasurePoint(
date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
value = it.value
)
}
)
)
)
}
} catch (err: Throwable) {
emit(Result.failure(BleException.UnexpectedResponse))
}
} else {
emit(Result.failure(BleException.PermissionDenied))
}
}
}

View File

@ -1,230 +0,0 @@
package llc.arma.ble.data
import android.Manifest
import android.app.Application
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothProfile
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.model.Ble
import java.util.UUID
class WriteThermometerCallback(
private val app: Application,
private var request: Ble.Thermometer.WriteRequest,
private val onResult: (Result<Unit, BleException>) -> Unit
) : BluetoothGattCallback() {
private var flashed = false
override fun onConnectionStateChange(
gatt: BluetoothGatt,
status: Int,
newState: Int
) {
super.onConnectionStateChange(gatt, status, newState)
if(app.checkPermission()) {
if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
} else {
onResult(Result.failure(BleException.UnexpectedResponse))
}
} else {
onResult(Result.failure(BleException.PermissionDenied))
}
}
override fun onServicesDiscovered(
gatt: BluetoothGatt,
status: Int
) {
super.onServicesDiscovered(gatt, status)
onCycle(gatt, status)
}
private fun onCycle(
gatt: BluetoothGatt,
status: Int
){
if(request.tx != null || request.saveHistory != null || request.historyInterval != null) {
fun UInt.to4ByteArrayInLittleEndian(): ByteArray =
(3 downTo 0).map {
(this shr (it * Byte.SIZE_BITS)).toByte()
}.toByteArray()
var uuid: Triple<UUID, ByteArray, Ble.Thermometer.WriteRequest>? = null
uuid = request.historyInterval?.let {
Triple(
intervalWriteUUID,
mutableListOf<Byte>(3).apply {
addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList())
}.toByteArray(),
request.copy(
historyInterval = null
)
)
}
uuid = request.saveHistory?.let {
Triple(
saveEnabledWriteUUID,
mutableListOf<Byte>(4).apply {
add(if (it) 1 else 0)
}.toByteArray(),
request.copy(
saveHistory = null
)
)
} ?: uuid
uuid = request.tx?.let {
Triple(
txWriteUUID,
byteArrayOf(
when (it) {
Ble.BleState.TX.MINUS_40 -> -40
Ble.BleState.TX.MINUS_20 -> -20
Ble.BleState.TX.MINUS_16 -> -16
Ble.BleState.TX.MINUS_12 -> -12
Ble.BleState.TX.MINUS_8 -> -8
Ble.BleState.TX.MINUS_4 -> -4
Ble.BleState.TX.ZERO -> 0
Ble.BleState.TX.PLUS_3 -> 3
Ble.BleState.TX.PLUS_4 -> 4
}
),
request.copy(
tx = null
)
)
} ?: uuid
uuid?.let { uuid ->
gatt.services.firstOrNull { it.uuid == serviceUUID }?.characteristics?.firstOrNull {
it.uuid == uuid.first
}?.let {
gatt.writeCharacteristic(it, uuid.second)
request = uuid.third
return
}
}
onResult(Result.failure(BleException.UnexpectedResponse))
} else {
if(flashed.not()){
flashed = true
gatt.services.firstOrNull { it.uuid == serviceUUID }?.characteristics?.firstOrNull {
it.uuid == flashWriteUUID
}?.let {
gatt.writeCharacteristic(it, byteArrayOf(9))
return
}
onResult(Result.failure(BleException.UnexpectedResponse))
} else {
onResult(Result.success(Unit))
}
}
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
super.onCharacteristicWrite(gatt, characteristic, status)
if(app.checkPermission()) {
if(status == BluetoothGatt.GATT_SUCCESS || flashed) {
onCycle(gatt, status)
} else {
onResult(Result.failure(BleException.UnexpectedResponse))
}
} else {
onResult(Result.failure(BleException.PermissionDenied))
}
}
private fun BluetoothGatt.writeCharacteristic(
characteristic: BluetoothGattCharacteristic,
data: ByteArray
): Result<Unit, BleException> {
return if(app.checkPermission()){
Log.d("write", data.asUByteArray().joinToString(" ") { it.toString(16).padStart(2, '0') })
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
}else{
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
characteristic.value = data
writeCharacteristic(characteristic)
}
Result.success(Unit)
} else {
Result.failure(BleException.PermissionDenied)
}
}
}