add rotations

This commit is contained in:
Vineyro 2024-06-11 17:00:17 +07:00
parent b915659217
commit a059cacda9
15 changed files with 1209 additions and 1991 deletions

View File

@ -13,8 +13,8 @@ android {
applicationId "llc.arma.ble" applicationId "llc.arma.ble"
minSdk 26 minSdk 26
targetSdk 34 targetSdk 34
versionCode 17 versionCode 18
versionName "1.2.17" versionName "1.2.18"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@ -86,6 +86,9 @@ dependencies {
kapt('com.google.dagger:hilt-android-compiler:2.45') kapt('com.google.dagger:hilt-android-compiler:2.45')
kapt("androidx.hilt:hilt-compiler:1.0.0") kapt("androidx.hilt:hilt-compiler:1.0.0")
implementation 'no.nordicsemi.android.kotlin.ble:scanner:1.0.14'
implementation 'no.nordicsemi.android.kotlin.ble:client:1.0.14'
implementation "com.google.accompanist:accompanist-permissions:0.26.3-beta" implementation "com.google.accompanist:accompanist-permissions:0.26.3-beta"
implementation "com.patrykandpatrick.vico:core:1.7.1" implementation "com.patrykandpatrick.vico:core:1.7.1"

View File

@ -24,16 +24,6 @@ import llc.arma.ble.domain.usecase.FftAxis
import llc.arma.ble.domain.usecase.FftFrequency import llc.arma.ble.domain.usecase.FftFrequency
import llc.arma.ble.domain.usecase.FftViewMode import llc.arma.ble.domain.usecase.FftViewMode
/*val AccelViewMode.localized: String
get() {
return when(this){
ACCELERATION -> "Ускорение"
PEAK_ACCELERATION -> "Пиковое ускорение"
RMS -> "Среднеквадратичное ускорение"
ANGLE -> "Угол"
}
}*/
@Composable @Composable
fun AccelFrequencyEdit( fun AccelFrequencyEdit(
state: AccelerometerContract.State.Display, state: AccelerometerContract.State.Display,

View File

@ -25,6 +25,7 @@ val AccelViewMode.localized: String
RMS -> "Среднеквадратичное ускорение" RMS -> "Среднеквадратичное ускорение"
VIBRATION -> "Вибрация" VIBRATION -> "Вибрация"
ANGLE -> "Угол" ANGLE -> "Угол"
ROTATIONS -> "Обороты"
} }
} }

View File

@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import llc.arma.ble.domain.usecase.AccelScale import llc.arma.ble.domain.usecase.AccelScale
import llc.arma.ble.domain.usecase.AccelViewMode import llc.arma.ble.domain.usecase.AccelViewMode
import llc.arma.ble.domain.usecase.AccelViewMode.*
import llc.arma.ble.domain.usecase.MeasureData import llc.arma.ble.domain.usecase.MeasureData
import llc.arma.ble.domain.usecase.FftAxis import llc.arma.ble.domain.usecase.FftAxis
import llc.arma.ble.domain.usecase.FftFrequency import llc.arma.ble.domain.usecase.FftFrequency
@ -194,126 +195,181 @@ fun Display(
val lastMeasure = state.measureHistory.lastOrNull() val lastMeasure = state.measureHistory.lastOrNull()
/*when(lastMeasure){
is MeasureData.Accelerate -> TODO()
is MeasureData.Angle -> TODO()
is MeasureData.Vibration -> {
}
null -> {}
}*/
if(lastMeasure is MeasureData.Accelerate) { if(lastMeasure is MeasureData.Accelerate) {
if(state.mode == AccelViewMode.ANGLE){ when(state.mode){
ROTATIONS -> {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Column( Column(
modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text(text = "Ось X: ${lastMeasure.x}")
Spacer(modifier = Modifier.height(8.dp))
Angle(
angle = lastMeasure.x,
modifier = Modifier.weight(1f),
)
}
Column(
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Ось Y: ${lastMeasure.y}") Text(text = "Ось Y: ${lastMeasure.y}")
Spacer(modifier = Modifier.height(8.dp)) Column(
Angle(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
angle = lastMeasure.y horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Ось X: ${lastMeasure.x}")
Spacer(modifier = Modifier.height(8.dp))
Angle(
angle = lastMeasure.x,
modifier = Modifier.weight(1f),
)
}
Text(text = "Ось Z:")
Chart(
chart = lineChart,
chartModelProducer = zProducer,
startAxis = startAxis(),
bottomAxis = bottomAxis(),
modifier = Modifier
.fillMaxWidth()
.weight(1f),
autoScaleUp = AutoScaleUp.None,
diffAnimationSpec = tween(0),
chartScrollSpec = rememberChartScrollSpec(
initialScroll = InitialScroll.End,
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
autoScrollAnimationSpec = tween(0)
)
) )
} }
}
ANGLE -> {
Column( Column(
modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text(text = "Ось Z: ${lastMeasure.z}") Column(
Spacer(modifier = Modifier.height(8.dp))
Angle(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
angle = lastMeasure.z horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Ось X: ${lastMeasure.x}")
Spacer(modifier = Modifier.height(8.dp))
Angle(
angle = lastMeasure.x,
modifier = Modifier.weight(1f),
)
}
Column(
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Ось Y: ${lastMeasure.y}")
Spacer(modifier = Modifier.height(8.dp))
Angle(
modifier = Modifier.weight(1f),
angle = lastMeasure.y
)
}
Column(
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Ось Z: ${lastMeasure.z}")
Spacer(modifier = Modifier.height(8.dp))
Angle(
modifier = Modifier.weight(1f),
angle = lastMeasure.z
)
}
}
}
else -> {
Column() {
Text(text = "Ось X:")
Chart(
chart = lineChart,
chartModelProducer = xProducer,
startAxis = startAxis(),
bottomAxis = bottomAxis(),
modifier = Modifier
.fillMaxWidth()
.weight(1f),
autoScaleUp = AutoScaleUp.None,
diffAnimationSpec = tween(0),
chartScrollSpec = rememberChartScrollSpec(
initialScroll = InitialScroll.End,
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
autoScrollAnimationSpec = tween(0)
)
)
Text(text = "Ось Y:")
Chart(
chart = lineChart,
chartModelProducer = yProducer,
startAxis = startAxis(),
bottomAxis = bottomAxis(),
modifier = Modifier
.fillMaxWidth()
.weight(1f),
autoScaleUp = AutoScaleUp.None,
diffAnimationSpec = tween(0),
chartScrollSpec = rememberChartScrollSpec(
initialScroll = InitialScroll.End,
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
autoScrollAnimationSpec = tween(0)
)
)
Text(text = "Ось Z:")
Chart(
chart = lineChart,
chartModelProducer = zProducer,
startAxis = startAxis(),
bottomAxis = bottomAxis(),
modifier = Modifier
.fillMaxWidth()
.weight(1f),
autoScaleUp = AutoScaleUp.None,
diffAnimationSpec = tween(0),
chartScrollSpec = rememberChartScrollSpec(
initialScroll = InitialScroll.End,
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
autoScrollAnimationSpec = tween(0)
)
) )
} }
} }
} else {
Column() {
Text(text = "Ось X:")
Chart(
chart = lineChart,
chartModelProducer = xProducer,
startAxis = startAxis(),
bottomAxis = bottomAxis(),
modifier = Modifier
.fillMaxWidth()
.weight(1f),
autoScaleUp = AutoScaleUp.None,
diffAnimationSpec = tween(0),
chartScrollSpec = rememberChartScrollSpec(
initialScroll = InitialScroll.End,
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
autoScrollAnimationSpec = tween(0)
)
)
Text(text = "Ось Y:")
Chart(
chart = lineChart,
chartModelProducer = yProducer,
startAxis = startAxis(),
bottomAxis = bottomAxis(),
modifier = Modifier
.fillMaxWidth()
.weight(1f),
autoScaleUp = AutoScaleUp.None,
diffAnimationSpec = tween(0),
chartScrollSpec = rememberChartScrollSpec(
initialScroll = InitialScroll.End,
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
autoScrollAnimationSpec = tween(0)
)
)
Text(text = "Ось Z:")
Chart(
chart = lineChart,
chartModelProducer = zProducer,
startAxis = startAxis(),
bottomAxis = bottomAxis(),
modifier = Modifier
.fillMaxWidth()
.weight(1f),
autoScaleUp = AutoScaleUp.None,
diffAnimationSpec = tween(0),
chartScrollSpec = rememberChartScrollSpec(
initialScroll = InitialScroll.End,
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
autoScrollAnimationSpec = tween(0)
)
)
}
} }
} else { } else {
@ -349,7 +405,7 @@ fun Display(
} }
@Composable @Composable
public fun Angle( fun Angle(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
angle: Float angle: Float
) { ) {
@ -493,7 +549,7 @@ class AccelerometerAccelViewModel @Inject constructor(
private var lastSerial: String? = null private var lastSerial: String? = null
override fun setInitialState() = AccelerometerAccelContract.State.Display( override fun setInitialState() = AccelerometerAccelContract.State.Display(
mode = AccelViewMode.ACCELERATION, mode = ACCELERATION,
measureHistory = emptyList() measureHistory = emptyList()
) )
@ -515,7 +571,7 @@ class AccelerometerAccelViewModel @Inject constructor(
setState { setState {
AccelerometerAccelContract.State.Display( AccelerometerAccelContract.State.Display(
mode = AccelViewMode.ACCELERATION, mode = ACCELERATION,
measureHistory = emptyList() measureHistory = emptyList()
) )
} }
@ -555,7 +611,7 @@ class AccelerometerAccelViewModel @Inject constructor(
setState { setState {
AccelerometerAccelContract.State.Display( AccelerometerAccelContract.State.Display(
mode = AccelViewMode.ACCELERATION, mode = ACCELERATION,
measureHistory = emptyList() measureHistory = emptyList()
) )
} }
@ -569,8 +625,8 @@ class AccelerometerAccelViewModel @Inject constructor(
var dataList = this.measureHistory.toMutableList().apply { var dataList = this.measureHistory.toMutableList().apply {
add(it) add(it)
} }
if(accelMode != AccelViewMode.ANGLE) { if(accelMode != ANGLE) {
dataList = dataList.takeLast(10).toMutableList() dataList = dataList.takeLast(100).toMutableList()
} }
AccelerometerAccelContract.State.Display(accelMode, dataList) AccelerometerAccelContract.State.Display(accelMode, dataList)
} }

View File

@ -44,19 +44,6 @@ val LightColorScheme = lightColorScheme(
surfaceTint = Color(0xFF755B00), surfaceTint = Color(0xFF755B00),
) )
@Composable
fun BleTheme(
colorScheme: ColorScheme,
content: @Composable () -> Unit
){
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content,
shapes = shapes
)
}
@Composable @Composable
fun BleTheme( fun BleTheme(

File diff suppressed because it is too large Load Diff

View File

@ -1,297 +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.BluetoothGattDescriptor
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.usecase.AccelScale
import llc.arma.ble.domain.usecase.AccelViewMode
import llc.arma.ble.domain.usecase.MeasureData
import llc.arma.ble.domain.usecase.FftAxis
import llc.arma.ble.domain.usecase.FftFrequency
import llc.arma.ble.domain.usecase.FftViewMode
import java.util.UUID
import kotlin.math.PI
import kotlin.math.atan
import kotlin.math.pow
import kotlin.math.sqrt
class ReadAccelerometerCallback(
private val app: Application,
private val accelScale: AccelScale,
private val accelMode: AccelViewMode,
private val fftAxis: FftAxis,
private val fftMode: FftViewMode,
private val frequency: FftFrequency,
private val onResult: (Result<MeasureData, BleException>) -> Unit
) : BluetoothGattCallback() {
override fun onConnectionStateChange(
gatt: BluetoothGatt,
status: Int,
newState: Int
) {
super.onConnectionStateChange(gatt, status, newState)
if(status == BluetoothGatt.GATT_SUCCESS){
if(newState == BluetoothGatt.STATE_CONNECTED){
if (checkPermission()) {
gatt.discoverServices()
} else {
onResult(Result.failure(BleException.UnexpectedResponse))
gatt.close()
}
}
} else {
onResult(Result.failure(BleException.UnexpectedResponse))
gatt.close()
}
}
override fun onServicesDiscovered(
gatt: BluetoothGatt,
status: Int
) {
super.onServicesDiscovered(gatt, status)
Log.d("accel", "onServicesDiscovered")
if(status == BluetoothGatt.GATT_SUCCESS){
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
if (checkPermission()) {
val payload = byteArrayOf(
4,
accelMode.sendData,
accelScale.sendData,
fftMode.sendData,
fftAxis.sendData,
frequency.sendData,
1
)
gatt.writeCharacteristic(it, payload)
} else {
onResult(Result.failure(BleException.PermissionDenied))
gatt.close()
}
}
}
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
super.onCharacteristicWrite(gatt, characteristic, status)
Log.d("accel", "request written")
if(status == BluetoothGatt.GATT_SUCCESS){
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
if (checkPermission()) {
gatt.setCharacteristicNotification(it, true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
gatt.writeDescriptor(it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
} else {
val descriptor = it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
} else {
onResult(Result.failure(BleException.PermissionDenied))
gatt.close()
}
}
}
}
@Deprecated("Deprecated in Java")
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
super.onCharacteristicChanged(gatt, characteristic)
onCommonCharacteristicRead(characteristic.value)
}
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray
) {
super.onCharacteristicChanged(gatt, characteristic, value)
onCommonCharacteristicRead(value)
}
private fun onCommonCharacteristicRead(
value: ByteArray,
){
val result = if(accelMode == AccelViewMode.VIBRATION){
MeasureData.Vibration((value.get2byteShortAt().toFloat() * accelScale.k) / Short.MAX_VALUE)
} else {
val data = value.toList().chunked(2).map {
it.toByteArray().get2byteShortAt()
}
Log.d("accel", "x: ${data[0]} y: ${data[1]} z: ${data[2]} bytes: ${value.joinToString { it.toString() }}")
val x: Float
val y: Float
val z: Float
if (accelMode == AccelViewMode.ANGLE) {
x = calculateZAngle(data[2].toFloat(), data[1].toFloat()) * 180f / Math.PI.toFloat()
y = calculateZAngle(data[2].toFloat(), data[0].toFloat()) * 180f / Math.PI.toFloat()
z = calculateZAngle(data[0].toFloat(), data[1].toFloat()) * 180f / Math.PI.toFloat()
} else {
x = (data[0].toFloat() * accelScale.k) / Short.MAX_VALUE
y = (data[1].toFloat() * accelScale.k) / Short.MAX_VALUE
z = (data[2].toFloat() * accelScale.k) / Short.MAX_VALUE
}
val state = MeasureData.Accelerate(
x = x,
y = y,
z = z
)
state
}
onResult(Result.success(result))
}
private fun checkPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
PackageManager.PERMISSION_GRANTED
} else {
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}
}
private fun BluetoothGatt.writeCharacteristic(
characteristic: BluetoothGattCharacteristic,
data: ByteArray
): Result<Unit, BleException>{
return if(checkPermission()){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
}else{
characteristic.value = data
writeCharacteristic(characteristic)
}
Result.success(Unit)
} else {
Result.failure(BleException.PermissionDenied)
}
}
}
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 {
var x = x
if(x == 0f && y == 0f){
x = 0.0001f
}
if(x > 0){
return atan(y/x)
}
if(x < 0 && y >= 0){
return atan(y/x) + PI.toFloat()
}
if(x < 0 && y < 0){
return atan(y/x) - PI.toFloat()
}
if(x == 0f && y > 0){
return PI.toFloat() / 2f
}
if(x == 0f && y < 0){
return -PI.toFloat() / 2f
}
return 0f
}

View File

@ -0,0 +1,228 @@
package llc.arma.ble.data
import android.Manifest
import android.app.Application
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
fun Application.checkPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) ==
PackageManager.PERMISSION_GRANTED
} else {
return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}
}
fun ByteArray.get4byteUIntAt(idx: Int) =
((this[idx + 3].toUInt() and 0xFFu) shl 24) or
((this[idx + 2].toUInt() and 0xFFu) shl 16) or
((this[idx + 1].toUInt() and 0xFFu) shl 8) or
(this[idx].toUInt() and 0xFFu)
/*fun ByteArray.get2byteUIntAt(idx: Int) =
((this[idx + 1].toUInt() and 0xFFu) shl 8) or
(this[idx].toUInt() and 0xFFu)*/
@OptIn(ExperimentalUnsignedTypes::class)
fun readAccelerometerHistory(
address: String,
mode: AccelViewMode,
scale: AccelScale,
app: Application,
): Flow<Result<ProgressState<List<Ble.Accelerometer.HistoryPoint>>, BleException>> {
return flow {
var lastMeasureSystemTime: Long? = null
var bleMeasureInterval: Long? = null
var bleRealTime: Long? = null
var bleLastMeasureTime: Long? = null
val resultTemperaturePackage: MutableList<Float> = mutableListOf()
val result = mutableListOf<List<UByte>>()
var expectedDataSize: Int? = null
if(app.checkPermission()) {
try {
val connection =
ClientBleGatt.connect(app, address, CoroutineScope(Dispatchers.Default))
val characteristic = connection.discoverServices()
.findService(serviceUUID)
?.findCharacteristic(accelerometerHistoryReadUUID)
if(characteristic != null) {
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)
}
result.add(value.toUByteArray().toList())
nextPackageDataCount = value.get2byteUIntAt(2)
resultTemperaturePackage.addAll(
temperatureDataArray.chunked(2).map {
it.toUByteArray().toByteArray().get2byteShortAt().toFloat()
}.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(
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
)
}
}
}
)
)
)
}
} else {
emit(Result.failure(BleException.UnexpectedResponse))
}
} catch (err: Throwable) {
emit(Result.failure(BleException.UnexpectedResponse))
}
} else {
emit(Result.failure(BleException.PermissionDenied))
}
}
}

View File

@ -1,410 +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.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
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
class ReadAccelerometerHistoryCallback(
private val mode: AccelViewMode,
private val scale: AccelScale,
private val app: Application,
private val onResult: (Result<ProgressState<List<Ble.Accelerometer.HistoryPoint>>, BleException>) -> Unit
) : BluetoothGattCallback() {
enum class Property {
DATA_SIZE, PACKAGE
}
private fun ByteArray.get4byteUIntAt(idx: Int) =
((this[idx + 3].toUInt() and 0xFFu) shl 24) or
((this[idx + 2].toUInt() and 0xFFu) shl 16) or
((this[idx + 1].toUInt() and 0xFFu) shl 8) or
(this[idx].toUInt() and 0xFFu)
private fun ByteArray.get2byteUIntAt(idx: Int) =
((this[idx + 1].toUInt() and 0xFFu) shl 8) or
(this[idx].toUInt() and 0xFFu)
private var readProperty: Property? = null
init {
Log.d("history", scale.name)
onResult(Result.success(ProgressState.Indeterminate))
}
override fun onConnectionStateChange(
gatt: BluetoothGatt,
status: Int,
newState: Int
) {
super.onConnectionStateChange(gatt, status, newState)
if(status == BluetoothGatt.GATT_SUCCESS){
if(newState == BluetoothGatt.STATE_CONNECTED){
if (checkPermission()) {
gatt.discoverServices()
} else {
onResult(Result.failure(BleException.UnexpectedResponse))
gatt.close()
}
}
} else {
onResult(Result.failure(BleException.UnexpectedResponse))
gatt.close()
}
}
override fun onServicesDiscovered(
gatt: BluetoothGatt,
status: Int
) {
super.onServicesDiscovered(gatt, status)
if(status == BluetoothGatt.GATT_SUCCESS){
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerHistoryReadUUID)?.let {
if (checkPermission()) {
readProperty = Property.DATA_SIZE
gatt.writeCharacteristic(it, byteArrayOf(2))
} else {
onResult(Result.failure(BleException.PermissionDenied))
gatt.close()
}
}
}
}
private var lastMeasureSystemTime: Long? = null
private var bleMeasureInterval: Long? = null
private var bleRealTime: Long? = null
private var bleLastMeasureTime: Long? = null
private val resultTemperaturePackage: MutableList<Float> = mutableListOf()
private val result = mutableListOf<List<UByte>>()
var expectedDataSize: Int? = null
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
super.onCharacteristicRead(gatt, characteristic, status)
onCommonCharacteristicRead(gatt, characteristic, characteristic.value, status)
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
status: Int
) {
super.onCharacteristicRead(gatt, characteristic, value, status)
onCommonCharacteristicRead(gatt, characteristic, value, status)
}
@OptIn(ExperimentalUnsignedTypes::class)
private fun onCommonCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
status: Int
){
if(status == BluetoothGatt.GATT_SUCCESS){
when(readProperty){
Property.DATA_SIZE -> {
if(value.contentEquals(byteArrayOf(0, 0))) {
onResult(
Result.success(
ProgressState.Finished(
emptyList()
)
)
)
gatt.close()
} else {
Log.d("expected data size", value.get2byteUIntAt(0).toString())
val writeData = mutableListOf(
1.toByte(),
0.toByte(),
0.toByte()
).apply {
addAll(value.toList())
}.toByteArray()
readProperty = Property.PACKAGE
gatt.writeCharacteristic(characteristic, writeData)
}
}
Property.PACKAGE -> {
if(value[0] == 250.toByte()){
result.add(value.toUByteArray().toList())
bleMeasureInterval = value.get4byteUIntAt(4).toLong()
bleLastMeasureTime = value.get4byteUIntAt(8).toLong()
bleRealTime = value.get4byteUIntAt(12).toLong()
lastMeasureSystemTime = System.currentTimeMillis() - ((bleRealTime!! - bleLastMeasureTime!!) * 1_000)
val nextPackageDataCount = value.get2byteUIntAt(2)
val temperatureDataArray = value.toUByteArray().asList().subList(16, value.size)
resultTemperaturePackage.addAll(
temperatureDataArray.chunked(2).map {
it.toUByteArray().toByteArray().get2byteShortAt().toFloat()
}.toMutableList()
)
Log.d("received data size", (temperatureDataArray.chunked(2).size).toString())
Log.d("next data size", nextPackageDataCount.toString())
expectedDataSize = nextPackageDataCount.toInt() + resultTemperaturePackage.size
onResult(Result.success(ProgressState.Progress(0f / expectedDataSize!!.toFloat())))
onResult(Result.success(ProgressState.Progress(resultTemperaturePackage.size.toFloat() / expectedDataSize!!.toFloat())))
if(nextPackageDataCount != 0.toUInt()){
if (checkPermission()) {
gatt.writeCharacteristic(characteristic, byteArrayOf(5))
gatt.readCharacteristic(characteristic)
} else {
onResult(Result.failure(BleException.PermissionDenied))
gatt.close()
}
} else {
onResult(
Result.success(
ProgressState.Finished(
when(mode){
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
)
}
}
}
)
)
)
gatt.close()
}
} else {
if (value[0] == 251.toByte()) {
result.add(value.toUByteArray().toList())
val nextPackageDataCount = value.get2byteUIntAt(2)
val temperatureDataArray = value.toUByteArray().toList().subList(4, value.size)
resultTemperaturePackage.addAll(
temperatureDataArray.chunked(2).map {
it.toUByteArray().toByteArray().get2byteShortAt().toFloat()
}
)
Log.d("received data size", (temperatureDataArray.chunked(2).size).toString())
Log.d("next data size", nextPackageDataCount.toString())
onResult(Result.success(ProgressState.Progress(resultTemperaturePackage.size.toFloat() / expectedDataSize!!.toFloat())))
if (nextPackageDataCount != 0.toUInt()) {
val writeData = byteArrayOf(5)
gatt.writeCharacteristic(characteristic, writeData)
gatt.readCharacteristic(characteristic)
} else {
onResult(
Result.success(
ProgressState.Finished(
when(mode){
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
)
}
}
}
)
)
)
gatt.close()
}
} else {
onResult(Result.failure(BleException.UnexpectedResponse))
gatt.close()
}
}
}
else -> {
onResult(Result.failure(BleException.UnexpectedResponse))
gatt.close()
}
}
}
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
super.onCharacteristicWrite(gatt, characteristic, status)
if(status == BluetoothGatt.GATT_SUCCESS){
if (checkPermission()) {
gatt.readCharacteristic(characteristic)
} else {
onResult(Result.failure(BleException.PermissionDenied))
gatt.close()
}
} else {
onResult(Result.failure(BleException.UnexpectedResponse))
gatt.close()
}
}
fun checkPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
PackageManager.PERMISSION_GRANTED
} else {
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}
}
fun BluetoothGatt.writeCharacteristic(
characteristic: BluetoothGattCharacteristic,
data: ByteArray
): Result<Unit, BleException>{
return if(checkPermission()){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
}else{
characteristic.value = data
writeCharacteristic(characteristic)
}
Result.success(Unit)
} else {
Result.failure(BleException.PermissionDenied)
}
}
}

View File

@ -10,6 +10,10 @@ import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import androidx.core.app.ActivityCompat 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.Result
import llc.arma.ble.domain.common.BleException import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState import llc.arma.ble.domain.common.ProgressState
@ -19,6 +23,8 @@ import llc.arma.ble.domain.usecase.AccelViewMode
import llc.arma.ble.domain.usecase.FftAxis import llc.arma.ble.domain.usecase.FftAxis
import llc.arma.ble.domain.usecase.FftFrequency import llc.arma.ble.domain.usecase.FftFrequency
import llc.arma.ble.domain.usecase.FftViewMode 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 java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder.LITTLE_ENDIAN import java.nio.ByteOrder.LITTLE_ENDIAN
import java.util.UUID import java.util.UUID
@ -70,7 +76,7 @@ class ReadAccelerometerSpectreCallback(
if(newState == BluetoothGatt.STATE_CONNECTED){ if(newState == BluetoothGatt.STATE_CONNECTED){
if (checkPermission()) { if (app.checkPermission()) {
gatt.discoverServices() gatt.discoverServices()
} else { } else {
onResult(Result.failure(BleException.UnexpectedResponse)) onResult(Result.failure(BleException.UnexpectedResponse))
@ -105,7 +111,7 @@ class ReadAccelerometerSpectreCallback(
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let { gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
if (checkPermission()) { if (app.checkPermission()) {
gatt.setCharacteristicNotification(it, true) gatt.setCharacteristicNotification(it, true)
@ -260,7 +266,7 @@ class ReadAccelerometerSpectreCallback(
if(nextPackageDataCount != 0.toUInt()){ if(nextPackageDataCount != 0.toUInt()){
if (checkPermission()) { if (app.checkPermission()) {
gatt.writeCharacteristic(characteristic, byteArrayOf(5)) gatt.writeCharacteristic(characteristic, byteArrayOf(5))
gatt.readCharacteristic(characteristic) gatt.readCharacteristic(characteristic)
@ -360,7 +366,7 @@ class ReadAccelerometerSpectreCallback(
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
if (checkPermission()) { if (app.checkPermission()) {
gatt.readCharacteristic(characteristic) gatt.readCharacteristic(characteristic)
@ -394,7 +400,7 @@ class ReadAccelerometerSpectreCallback(
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let { gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
if (checkPermission()) { if (app.checkPermission()) {
val payload = byteArrayOf( val payload = byteArrayOf(
4, 4,
@ -424,27 +430,12 @@ class ReadAccelerometerSpectreCallback(
} }
private fun checkPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
PackageManager.PERMISSION_GRANTED
} else {
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}
}
private fun BluetoothGatt.writeCharacteristic( private fun BluetoothGatt.writeCharacteristic(
characteristic: BluetoothGattCharacteristic, characteristic: BluetoothGattCharacteristic,
data: ByteArray data: ByteArray
): Result<Unit, BleException>{ ): Result<Unit, BleException>{
return if(checkPermission()){ return if(app.checkPermission()){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
@ -462,3 +453,187 @@ class ReadAccelerometerSpectreCallback(
} }
} }
/*
@OptIn(ExperimentalUnsignedTypes::class)
fun readAccelerometerSpectre(
address: String,
mode: AccelViewMode,
scale: AccelScale,
app: Application,
): 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
val resultTemperaturePackage: MutableList<Float> = mutableListOf()
val result = mutableListOf<List<UByte>>()
var expectedDataSize: Int? = null
if(app.checkPermission()) {
try {
val connection =
ClientBleGatt.connect(app, address, CoroutineScope(Dispatchers.Default))
val characteristic = connection.discoverServices()
.findService(serviceUUID)
?.findCharacteristic(accelerometerHistoryReadUUID)
if(characteristic != null) {
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)
}
result.add(value.toUByteArray().toList())
nextPackageDataCount = value.get2byteUIntAt(2)
resultTemperaturePackage.addAll(
temperatureDataArray.chunked(2).map {
it.toUByteArray().toByteArray().get2byteShortAt().toFloat()
}.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(
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
)
}
}
}
)
)
)
}
} else {
emit(Result.failure(BleException.UnexpectedResponse))
}
} catch (err: Throwable) {
emit(Result.failure(BleException.UnexpectedResponse))
}
} else {
emit(Result.failure(BleException.PermissionDenied))
}
}
}*/

View File

@ -50,7 +50,7 @@ class ReadTemperatureHistoryCallback(
if(newState == BluetoothGatt.STATE_CONNECTED){ if(newState == BluetoothGatt.STATE_CONNECTED){
if (checkPermission()) { if (app.checkPermission()) {
gatt.discoverServices() gatt.discoverServices()
} else { } else {
@ -76,7 +76,7 @@ class ReadTemperatureHistoryCallback(
if(status == BluetoothGatt.GATT_SUCCESS){ if(status == BluetoothGatt.GATT_SUCCESS){
gatt.getService(serviceUUID)?.getCharacteristic(temperatureHistoryReadUUID)?.let { gatt.getService(serviceUUID)?.getCharacteristic(temperatureHistoryReadUUID)?.let {
if (checkPermission()) { if (app.checkPermission()) {
readProperty = Property.DATA_SIZE readProperty = Property.DATA_SIZE
gatt.writeCharacteristic(it, byteArrayOf(2)) gatt.writeCharacteristic(it, byteArrayOf(2))
@ -188,7 +188,7 @@ class ReadTemperatureHistoryCallback(
if(nextPackageDataCount != 0.toUInt()){ if(nextPackageDataCount != 0.toUInt()){
if (checkPermission()) { if (app.checkPermission()) {
gatt.writeCharacteristic(characteristic, byteArrayOf(5)) gatt.writeCharacteristic(characteristic, byteArrayOf(5))
gatt.readCharacteristic(characteristic) gatt.readCharacteristic(characteristic)
@ -279,7 +279,7 @@ class ReadTemperatureHistoryCallback(
super.onCharacteristicWrite(gatt, characteristic, status) super.onCharacteristicWrite(gatt, characteristic, status)
if(status == BluetoothGatt.GATT_SUCCESS){ if(status == BluetoothGatt.GATT_SUCCESS){
if (checkPermission()) { if (app.checkPermission()) {
gatt.readCharacteristic(characteristic) gatt.readCharacteristic(characteristic)
@ -297,27 +297,12 @@ class ReadTemperatureHistoryCallback(
} }
fun checkPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
PackageManager.PERMISSION_GRANTED
} else {
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}
}
fun BluetoothGatt.writeCharacteristic( fun BluetoothGatt.writeCharacteristic(
characteristic: BluetoothGattCharacteristic, characteristic: BluetoothGattCharacteristic,
data: ByteArray data: ByteArray
): Result<Unit, BleException>{ ): Result<Unit, BleException>{
return if(checkPermission()){ return if(app.checkPermission()){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)

View File

@ -1,245 +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 llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.model.Ble
import java.util.UUID
class WriteAccelerometerCallback(
private val app: Application,
private var request: Ble.Accelerometer.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(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
){
Log.d("write", "${request.tx != null} ${request.saveHistory != null} ${request.historyInterval != null}")
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.Accelerometer.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 is Ble.Accelerometer.History.Enabled) 1 else 0)
if(it is Ble.Accelerometer.History.Enabled) {
add(it.mode.sendData)
add(it.scale.sendData)
}
}.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(checkPermission()) {
if(status == BluetoothGatt.GATT_SUCCESS || flashed) {
onCycle(gatt, status)
} else {
onResult(Result.failure(BleException.UnexpectedResponse))
}
} else {
onResult(Result.failure(BleException.PermissionDenied))
}
}
fun BluetoothGatt.writeCharacteristic(
characteristic: BluetoothGattCharacteristic,
data: ByteArray
): Result<Unit, BleException> {
return if(checkPermission()){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
}else{
characteristic.writeType
characteristic.value = data
writeCharacteristic(characteristic)
}
Result.success(Unit)
} else {
Result.failure(BleException.PermissionDenied)
}
}
fun checkPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
PackageManager.PERMISSION_GRANTED
} else {
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}
}
}

View File

@ -1,209 +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 llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.model.Ble
import java.util.UUID
class WriteBeaconCallback(
private val app: Application,
private var request: Ble.Beacon.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(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) {
var uuid: Pair<UUID, ByteArray>? = null
uuid = request.tx?.let {
this.request = request.copy(
tx = null
)
Pair(
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
}
)
)
} ?: uuid
uuid?.let { uuid ->
gatt.services.firstOrNull { it.uuid == serviceUUID }?.characteristics?.firstOrNull {
it.uuid == uuid.first
}?.let {
gatt.writeCharacteristic(it, uuid.second)
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
) {
Log.d("beacon", "onCharacteristicWrite $status")
super.onCharacteristicWrite(gatt, characteristic, status)
if(checkPermission()) {
if(status == BluetoothGatt.GATT_SUCCESS || flashed) {
onCycle(gatt, status)
} else {
onResult(Result.failure(BleException.UnexpectedResponse))
}
} else {
onResult(Result.failure(BleException.PermissionDenied))
}
}
fun BluetoothGatt.writeCharacteristic(
characteristic: BluetoothGattCharacteristic,
data: ByteArray
): Result<Unit, BleException> {
return if(checkPermission()){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
}else{
characteristic.writeType
characteristic.value = data
writeCharacteristic(characteristic)
}
Result.success(Unit)
} else {
Result.failure(BleException.PermissionDenied)
}
}
fun checkPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
PackageManager.PERMISSION_GRANTED
} else {
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}
}
}

View File

@ -34,7 +34,7 @@ class WriteThermometerCallback(
) { ) {
super.onConnectionStateChange(gatt, status, newState) super.onConnectionStateChange(gatt, status, newState)
if(checkPermission()) { if(app.checkPermission()) {
if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
@ -179,7 +179,7 @@ class WriteThermometerCallback(
super.onCharacteristicWrite(gatt, characteristic, status) super.onCharacteristicWrite(gatt, characteristic, status)
if(checkPermission()) { if(app.checkPermission()) {
if(status == BluetoothGatt.GATT_SUCCESS || flashed) { if(status == BluetoothGatt.GATT_SUCCESS || flashed) {
@ -204,7 +204,7 @@ class WriteThermometerCallback(
data: ByteArray data: ByteArray
): Result<Unit, BleException> { ): Result<Unit, BleException> {
return if(checkPermission()){ return if(app.checkPermission()){
Log.d("write", data.asUByteArray().joinToString(" ") { it.toString(16).padStart(2, '0') }) Log.d("write", data.asUByteArray().joinToString(" ") { it.toString(16).padStart(2, '0') })
@ -225,19 +225,6 @@ class WriteThermometerCallback(
} }
private fun checkPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
PackageManager.PERMISSION_GRANTED
} else {
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}
}
} }

View File

@ -26,7 +26,7 @@ class GetAccelerometerMeasureBySerialFlow @Inject constructor(
} }
enum class AccelViewMode { enum class AccelViewMode {
ACCELERATION, PEAK_ACCELERATION, RMS, VIBRATION, ANGLE ACCELERATION, PEAK_ACCELERATION, RMS, VIBRATION, ANGLE, ROTATIONS
} }
enum class AccelScale(val k: Int) { enum class AccelScale(val k: Int) {