Экспорт
This commit is contained in:
parent
12df287193
commit
bc569581ad
|
|
@ -11,10 +11,10 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "llc.arma.ble"
|
||||
minSdk 24
|
||||
minSdk 26
|
||||
targetSdk 33
|
||||
versionCode 9
|
||||
versionName "1.2.9"
|
||||
versionCode 10
|
||||
versionName "1.2.10"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
|
@ -87,4 +87,6 @@ dependencies {
|
|||
implementation "com.patrykandpatrick.vico:compose:1.7.1"
|
||||
implementation "com.patrykandpatrick.vico:compose-m3:1.7.1"
|
||||
|
||||
implementation files('libs/poishadow-all.jar')
|
||||
|
||||
}
|
||||
Binary file not shown.
|
|
@ -29,6 +29,18 @@
|
|||
tools:targetApi="31"
|
||||
android:name=".app.framework.App">
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="llc.arma.ble.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
|
||||
</provider>
|
||||
|
||||
<activity
|
||||
android:name=".app.ui.MainActivity"
|
||||
android:exported="true"
|
||||
|
|
|
|||
|
|
@ -5,4 +5,18 @@ import dagger.hilt.android.HiltAndroidApp
|
|||
|
||||
@HiltAndroidApp()
|
||||
class App : Application() {
|
||||
|
||||
/**
|
||||
*Без этого apache poi на android не заводится
|
||||
*@link https://github.com/centic9/poi-on-android
|
||||
*/
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
System.setProperty("org.apache.poi.javax.xml.stream.XMLInputFactory", "com.fasterxml.aalto.stax.InputFactoryImpl");
|
||||
System.setProperty("org.apache.poi.javax.xml.stream.XMLOutputFactory", "com.fasterxml.aalto.stax.OutputFactoryImpl");
|
||||
System.setProperty("org.apache.poi.javax.xml.stream.XMLEventFactory", "com.fasterxml.aalto.stax.EventFactoryImpl");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,7 +5,11 @@ import dagger.Module
|
|||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import llc.arma.ble.data.BleRepositoryImpl
|
||||
import llc.arma.ble.data.EmailRepositoryImpl
|
||||
import llc.arma.ble.data.XlsxRepositoryImpl
|
||||
import llc.arma.ble.domain.repository.BleRepository
|
||||
import llc.arma.ble.domain.repository.EmailRepository
|
||||
import llc.arma.ble.domain.repository.XlsxRepository
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
|
|
@ -14,4 +18,10 @@ interface RepositoryBinding {
|
|||
@Binds
|
||||
fun bindBleRepository(bleRepositoryImpl: BleRepositoryImpl): BleRepository
|
||||
|
||||
@Binds
|
||||
fun bindEmailRepository(repository: EmailRepositoryImpl): EmailRepository
|
||||
|
||||
@Binds
|
||||
fun bindXlsxRepository(repository: XlsxRepositoryImpl): XlsxRepository
|
||||
|
||||
}
|
||||
|
|
@ -7,14 +7,17 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
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
|
||||
class BleListViewModel @Inject constructor(
|
||||
getBleAroundFlow: GetBleAroundFlow,
|
||||
getConnectedBleDevices: GetConnectedBleDevices
|
||||
getConnectedBleDevices: GetConnectedBleDevices,
|
||||
exportToXlsx: ExportToXlsx
|
||||
) : BaseViewModel<BleListContract.State, BleListContract.Event, BleListContract.Effect>() {
|
||||
|
||||
init {
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@ fun Display(
|
|||
producer.setEntries(
|
||||
data.mapIndexed { index, measurePoint ->
|
||||
AccelerometerEntry(measurePoint.frequency, index.toFloat(), measurePoint.value)
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -345,12 +345,11 @@ fun Display(
|
|||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Angle(
|
||||
public fun Angle(
|
||||
modifier: Modifier = Modifier,
|
||||
angle: Float
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import llc.arma.ble.app.ui.common.ViewState
|
|||
import llc.arma.ble.domain.model.BleInfo
|
||||
import javax.inject.Inject
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.CloudUpload
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
|
@ -31,6 +32,8 @@ import com.patrykandpatrick.vico.compose.chart.line.lineChart
|
|||
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollSpec
|
||||
import com.patrykandpatrick.vico.core.axis.AxisPosition
|
||||
import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter
|
||||
import com.patrykandpatrick.vico.core.chart.decoration.ThresholdLine
|
||||
import com.patrykandpatrick.vico.core.chart.scale.AutoScaleUp
|
||||
import com.patrykandpatrick.vico.core.entry.ChartEntry
|
||||
import com.patrykandpatrick.vico.core.entry.FloatEntry
|
||||
import com.patrykandpatrick.vico.core.scroll.AutoScrollCondition
|
||||
|
|
@ -43,10 +46,12 @@ 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 llc.arma.ble.domain.usecase.ExportToXlsx
|
||||
import llc.arma.ble.domain.usecase.FftAxis
|
||||
import llc.arma.ble.domain.usecase.FftFrequency
|
||||
import llc.arma.ble.domain.usecase.FftViewMode
|
||||
import llc.arma.ble.domain.usecase.GetAccelerometerHistoryBySerial
|
||||
import llc.arma.ble.domain.usecase.MeasureData
|
||||
import java.util.Date
|
||||
|
||||
class AccelEntry(
|
||||
|
|
@ -108,6 +113,21 @@ fun AccelerometerHistory(
|
|||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
viewModel.setEvent(AccelerometerHistoryContract.Event.OnExport)
|
||||
},
|
||||
enabled = when(state){
|
||||
is AccelerometerHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
|
||||
AccelerometerHistoryContract.State.Exception -> false
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.CloudUpload,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
viewModel.setEvent(AccelerometerHistoryContract.Event.OnRefreshHistory(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
|
||||
|
|
@ -164,14 +184,6 @@ fun Display(
|
|||
|
||||
} else {
|
||||
|
||||
val producer = remember {
|
||||
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||
}
|
||||
|
||||
producer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
|
||||
AccelEntry(measurePoint.frequency, index.toFloat(), measurePoint.value )
|
||||
})
|
||||
|
||||
val axisValueFormatter =
|
||||
AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
|
||||
(chartValues.chartEntryModel.entries.firstOrNull()
|
||||
|
|
@ -181,25 +193,174 @@ fun Display(
|
|||
.orEmpty()
|
||||
}
|
||||
|
||||
val lineChart = lineChart()
|
||||
val xProducer = remember {
|
||||
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||
}
|
||||
|
||||
Chart(
|
||||
chart = lineChart,
|
||||
chartModelProducer = producer,
|
||||
startAxis = startAxis(),
|
||||
bottomAxis = bottomAxis(
|
||||
tickLength = 0.dp,
|
||||
valueFormatter = axisValueFormatter,
|
||||
labelRotationDegrees = -90f,
|
||||
),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
chartScrollSpec = rememberChartScrollSpec(
|
||||
initialScroll = InitialScroll.End,
|
||||
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||
autoScrollAnimationSpec = tween(0)
|
||||
val yProducer = remember {
|
||||
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||
}
|
||||
|
||||
val zProducer = remember {
|
||||
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||
}
|
||||
|
||||
xProducer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
|
||||
when(measurePoint){
|
||||
is Ble.Accelerometer.HistoryPoint.Accelerate -> {
|
||||
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.x )
|
||||
}
|
||||
is Ble.Accelerometer.HistoryPoint.Vibration -> {
|
||||
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.value)
|
||||
}
|
||||
is Ble.Accelerometer.HistoryPoint.Angle -> {
|
||||
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.x )
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
yProducer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
|
||||
when(measurePoint){
|
||||
is Ble.Accelerometer.HistoryPoint.Accelerate -> {
|
||||
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.y )
|
||||
}
|
||||
is Ble.Accelerometer.HistoryPoint.Vibration -> {
|
||||
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.value)
|
||||
}
|
||||
is Ble.Accelerometer.HistoryPoint.Angle -> {
|
||||
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.y )
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
zProducer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
|
||||
when(measurePoint){
|
||||
is Ble.Accelerometer.HistoryPoint.Accelerate -> {
|
||||
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.z )
|
||||
}
|
||||
is Ble.Accelerometer.HistoryPoint.Vibration -> {
|
||||
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.value)
|
||||
}
|
||||
is Ble.Accelerometer.HistoryPoint.Angle -> {
|
||||
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.z )
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
val lineChart = lineChart(
|
||||
decorations = listOf(
|
||||
ThresholdLine(
|
||||
thresholdValue = 0f
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val lastMeasure = state.loadingHistoryState.data.lastOrNull()
|
||||
|
||||
if((lastMeasure is Ble.Accelerometer.HistoryPoint.Vibration).not()) {
|
||||
|
||||
Column() {
|
||||
|
||||
Text(text = "Ось X:")
|
||||
|
||||
Chart(
|
||||
chart = lineChart,
|
||||
chartModelProducer = xProducer,
|
||||
startAxis = startAxis(),
|
||||
bottomAxis = bottomAxis(
|
||||
tickLength = 0.dp,
|
||||
valueFormatter = axisValueFormatter,
|
||||
labelRotationDegrees = -90f,
|
||||
),
|
||||
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(
|
||||
tickLength = 0.dp,
|
||||
valueFormatter = axisValueFormatter,
|
||||
labelRotationDegrees = -90f,
|
||||
),
|
||||
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(
|
||||
tickLength = 0.dp,
|
||||
valueFormatter = axisValueFormatter,
|
||||
labelRotationDegrees = -90f,
|
||||
),
|
||||
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 = "Вибрация:")
|
||||
|
||||
Chart(
|
||||
chart = lineChart,
|
||||
chartModelProducer = xProducer,
|
||||
startAxis = startAxis(),
|
||||
bottomAxis = bottomAxis(
|
||||
tickLength = 0.dp,
|
||||
valueFormatter = axisValueFormatter,
|
||||
labelRotationDegrees = -90f,
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
autoScaleUp = AutoScaleUp.None,
|
||||
diffAnimationSpec = tween(0),
|
||||
chartScrollSpec = rememberChartScrollSpec(
|
||||
initialScroll = InitialScroll.End,
|
||||
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||
autoScrollAnimationSpec = tween(0)
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -261,6 +422,8 @@ class AccelerometerHistoryContract {
|
|||
|
||||
object StopMeasure : Event()
|
||||
|
||||
object OnExport : Event()
|
||||
|
||||
data class OnStart(
|
||||
val serial: String,
|
||||
val accelScale: AccelScale,
|
||||
|
|
@ -284,7 +447,7 @@ class AccelerometerHistoryContract {
|
|||
sealed class State : ViewState {
|
||||
|
||||
data class Display(
|
||||
val loadingHistoryState : ProgressState<List<Ble.Accelerometer.MeasurePoint>>
|
||||
val loadingHistoryState : ProgressState<List<Ble.Accelerometer.HistoryPoint>>
|
||||
) : State()
|
||||
|
||||
object Exception : State()
|
||||
|
|
@ -302,6 +465,7 @@ class AccelerometerHistoryContract {
|
|||
@HiltViewModel
|
||||
class AccelerometerHistoryViewModel @Inject constructor(
|
||||
private val getAccelerometerHistoryBySerial: GetAccelerometerHistoryBySerial,
|
||||
private val exportToXlsx: ExportToXlsx
|
||||
) : BaseViewModel<AccelerometerHistoryContract.State, AccelerometerHistoryContract.Event, AccelerometerHistoryContract.Effect>() {
|
||||
|
||||
var measureJob: Job? = null
|
||||
|
|
@ -317,15 +481,28 @@ class AccelerometerHistoryViewModel @Inject constructor(
|
|||
is AccelerometerHistoryContract.Event.OnStart -> reduce(viewState.value, event)
|
||||
is AccelerometerHistoryContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
|
||||
is AccelerometerHistoryContract.Event.StopMeasure -> reduce(viewState.value, event)
|
||||
is AccelerometerHistoryContract.Event.OnExport -> reduce(viewState.value, event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: AccelerometerHistoryContract.State,
|
||||
event: AccelerometerHistoryContract.Event.OnExport
|
||||
) {
|
||||
|
||||
if(state is AccelerometerHistoryContract.State.Display){
|
||||
if(state.loadingHistoryState is ProgressState.Finished){
|
||||
exportToXlsx.invoke(state.loadingHistoryState.data)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: AccelerometerHistoryContract.State,
|
||||
event: AccelerometerHistoryContract.Event.StopMeasure
|
||||
) {
|
||||
|
||||
|
||||
measureJob?.cancel()
|
||||
measureJob = null
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ fun IntervalEdit(
|
|||
){
|
||||
|
||||
var value by remember(state.accelerometerState.historyInterval) {
|
||||
mutableStateOf((state.accelerometerState.historyInterval / 1000 / 60 / 60).toInt())
|
||||
mutableIntStateOf((state.accelerometerState.historyInterval).toInt())
|
||||
}
|
||||
|
||||
val maxInterval = 10 * 24 * 60 * 60 * 1000
|
||||
|
|
|
|||
|
|
@ -142,13 +142,17 @@ fun Write(
|
|||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
|
||||
val hours = it / 1000 / 60 / 60
|
||||
val minutes = (it - ( hours * 1000 * 60 * 60 )) / 1000 / 60
|
||||
|
||||
Text(
|
||||
text = "Интервал измерний"
|
||||
text = "Интервал измерений"
|
||||
)
|
||||
|
||||
Text(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = "${it / 1000 / 60 / 60} ч."
|
||||
text = "$hours ч. $minutes мин."
|
||||
)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,13 +137,17 @@ fun Write(
|
|||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
|
||||
val hours = it / 1000 / 60 / 60
|
||||
val minutes = (it - ( hours * 1000 * 60 * 60 )) / 1000 / 60
|
||||
|
||||
Text(
|
||||
text = "Интервал измерний"
|
||||
text = "Интервал измерений"
|
||||
)
|
||||
|
||||
Text(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = "${it / 1000 / 60 / 60} ч."
|
||||
text = "$hours ч. $minutes мин."
|
||||
)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -542,7 +542,6 @@ class BleRepositoryImpl @Inject constructor(
|
|||
serviceId = serviceUUID,
|
||||
characteristicId = accelerometerReadUUID,
|
||||
).fold(
|
||||
|
||||
onSuccess = {
|
||||
Log.d("history", it.joinToString { it.toString() })
|
||||
val scale = when(it[1].toInt()){
|
||||
|
|
@ -559,6 +558,7 @@ class BleRepositoryImpl @Inject constructor(
|
|||
1 -> AccelViewMode.PEAK_ACCELERATION
|
||||
2 -> AccelViewMode.RMS
|
||||
3 -> AccelViewMode.VIBRATION
|
||||
4 -> AccelViewMode.ANGLE
|
||||
else -> {
|
||||
return Result.failure(BleException.UnexpectedResponse)
|
||||
}
|
||||
|
|
@ -572,7 +572,6 @@ class BleRepositoryImpl @Inject constructor(
|
|||
onFailure = {
|
||||
return Result.failure(BleException.UnexpectedResponse)
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
false -> Ble.Accelerometer.History.Disabled
|
||||
|
|
@ -738,17 +737,31 @@ class BleRepositoryImpl @Inject constructor(
|
|||
|
||||
override suspend fun getAccelerometerHistoryBySerial(
|
||||
serial: String
|
||||
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {
|
||||
): Flow<Result<ProgressState<List<Ble.Accelerometer.HistoryPoint>>, BleException>> {
|
||||
|
||||
var gatt: BluetoothGatt? = null
|
||||
|
||||
return callbackFlow {
|
||||
|
||||
deviceCache[serial]?.device?.let { device ->
|
||||
deviceCache[serial]?.let { result ->
|
||||
|
||||
val device = result.device
|
||||
|
||||
if (checkPermission()) {
|
||||
|
||||
val scale = writeCharacteristic(
|
||||
val state = readAccelState(result).fold(
|
||||
onSuccess = {
|
||||
|
||||
},
|
||||
onFailure = {
|
||||
null
|
||||
}
|
||||
)
|
||||
|
||||
var scale: AccelScale? = null
|
||||
var mode: AccelViewMode? = null
|
||||
|
||||
writeCharacteristic(
|
||||
device = device,
|
||||
serviceId = serviceUUID,
|
||||
characteristicId = accelerometerReadUUID,
|
||||
|
|
@ -761,13 +774,21 @@ class BleRepositoryImpl @Inject constructor(
|
|||
characteristicId = accelerometerReadUUID,
|
||||
).fold(
|
||||
onSuccess = {
|
||||
when(it[1].toInt()){
|
||||
scale = when(it[1].toInt()){
|
||||
0 -> AccelScale.S_2
|
||||
1 -> AccelScale.S_4
|
||||
2 -> AccelScale.S_8
|
||||
3 -> AccelScale.S_16
|
||||
else -> null
|
||||
}
|
||||
mode = when(it[0].toInt()){
|
||||
0 -> AccelViewMode.ACCELERATION
|
||||
1 -> AccelViewMode.PEAK_ACCELERATION
|
||||
2 -> AccelViewMode.RMS
|
||||
3 -> AccelViewMode.VIBRATION
|
||||
4 -> AccelViewMode.ANGLE
|
||||
else -> null
|
||||
}
|
||||
},
|
||||
onFailure = { null }
|
||||
)
|
||||
|
|
@ -777,12 +798,12 @@ class BleRepositoryImpl @Inject constructor(
|
|||
}
|
||||
)
|
||||
|
||||
if(scale != null) {
|
||||
if(scale != null && mode != null) {
|
||||
|
||||
gatt = device.connectGatt(
|
||||
app,
|
||||
false,
|
||||
ReadAccelerometerHistoryCallback(scale, app) {
|
||||
ReadAccelerometerHistoryCallback(mode!!, scale!!, app) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
send(it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package llc.arma.ble.data
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import androidx.core.content.FileProvider
|
||||
import llc.arma.ble.R
|
||||
import llc.arma.ble.domain.repository.EmailRepository
|
||||
import llc.arma.ble.domain.repository.XlsxRepository
|
||||
import llc.arma.ble.domain.usecase.MeasureData
|
||||
import org.apache.poi.ss.SpreadsheetVersion
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory
|
||||
import org.apache.poi.ss.util.AreaReference
|
||||
import org.apache.poi.ss.util.CellReference
|
||||
import org.apache.poi.util.IOUtils
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
class EmailRepositoryImpl @Inject constructor(
|
||||
private val application: Application
|
||||
) : EmailRepository {
|
||||
|
||||
override fun sendFile(file: File) {
|
||||
val uri = FileProvider.getUriForFile(application, "llc.arma.ble.fileprovider", file)
|
||||
|
||||
val sendIntent = Intent(Intent.ACTION_SEND)
|
||||
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
sendIntent.type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
sendIntent.putExtra(Intent.EXTRA_SUBJECT, "Measure history")
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
application.startActivity(
|
||||
Intent.createChooser(sendIntent, "Send email...").apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -261,7 +261,7 @@ fun calculateAngle(
|
|||
|
||||
}
|
||||
|
||||
fun calculateZAngle(
|
||||
public fun calculateZAngle(
|
||||
x: Float,
|
||||
y: Float
|
||||
): Float {
|
||||
|
|
|
|||
|
|
@ -14,11 +14,13 @@ 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.MeasurePoint>>, BleException>) -> Unit
|
||||
private val onResult: (Result<ProgressState<List<Ble.Accelerometer.HistoryPoint>>, BleException>) -> Unit
|
||||
) : BluetoothGattCallback() {
|
||||
|
||||
enum class Property {
|
||||
|
|
@ -218,11 +220,37 @@ class ReadAccelerometerHistoryCallback(
|
|||
onResult(
|
||||
Result.success(
|
||||
ProgressState.Finished(
|
||||
resultTemperaturePackage.withIndex().map {
|
||||
Ble.Accelerometer.MeasurePoint(
|
||||
frequency = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
|
||||
value = (it.value * scale.k) / Short.MAX_VALUE
|
||||
)
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
|
@ -261,11 +289,37 @@ class ReadAccelerometerHistoryCallback(
|
|||
onResult(
|
||||
Result.success(
|
||||
ProgressState.Finished(
|
||||
resultTemperaturePackage.withIndex().map {
|
||||
Ble.Accelerometer.MeasurePoint(
|
||||
frequency = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
|
||||
value = (it.value * scale.k) / Short.MAX_VALUE
|
||||
)
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -64,36 +64,33 @@ class WriteAccelerometerCallback(
|
|||
status: Int
|
||||
){
|
||||
|
||||
if(request.tx != null || request.saveHistory != null) {
|
||||
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: Pair<UUID, ByteArray>? = null
|
||||
var uuid: Triple<UUID, ByteArray, Ble.Accelerometer.WriteRequest>? = null
|
||||
|
||||
uuid = request.historyInterval?.let {
|
||||
|
||||
this.request = request.copy(
|
||||
historyInterval = null
|
||||
)
|
||||
|
||||
Pair(
|
||||
Triple(
|
||||
intervalWriteUUID,
|
||||
mutableListOf<Byte>(3).apply {
|
||||
addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList())
|
||||
}.toByteArray()
|
||||
}.toByteArray(),
|
||||
request.copy(
|
||||
historyInterval = null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
uuid = request.saveHistory?.let {
|
||||
|
||||
this.request = request.copy(
|
||||
saveHistory = null
|
||||
)
|
||||
|
||||
Pair(
|
||||
Triple(
|
||||
saveEnabledWriteUUID,
|
||||
mutableListOf<Byte>(4).apply {
|
||||
add(if (it is Ble.Accelerometer.History.Enabled) 1 else 0)
|
||||
|
|
@ -101,17 +98,16 @@ class WriteAccelerometerCallback(
|
|||
add(it.mode.sendData)
|
||||
add(it.scale.sendData)
|
||||
}
|
||||
}.toByteArray()
|
||||
}.toByteArray(),
|
||||
request.copy(
|
||||
saveHistory = null
|
||||
)
|
||||
)
|
||||
} ?: uuid
|
||||
|
||||
uuid = request.tx?.let {
|
||||
|
||||
this.request = request.copy(
|
||||
tx = null
|
||||
)
|
||||
|
||||
Pair(
|
||||
Triple(
|
||||
txWriteUUID,
|
||||
byteArrayOf(
|
||||
when (it) {
|
||||
|
|
@ -125,6 +121,9 @@ class WriteAccelerometerCallback(
|
|||
Ble.BleState.TX.PLUS_3 -> 3
|
||||
Ble.BleState.TX.PLUS_4 -> 4
|
||||
}
|
||||
),
|
||||
request.copy(
|
||||
tx = null
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -138,6 +137,8 @@ class WriteAccelerometerCallback(
|
|||
|
||||
gatt.writeCharacteristic(it, uuid.second)
|
||||
|
||||
request = uuid.third
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,43 +75,37 @@ class WriteThermometerCallback(
|
|||
(this shr (it * Byte.SIZE_BITS)).toByte()
|
||||
}.toByteArray()
|
||||
|
||||
var uuid: Pair<UUID, ByteArray>? = null
|
||||
var uuid: Triple<UUID, ByteArray, Ble.Thermometer.WriteRequest>? = null
|
||||
|
||||
uuid = request.historyInterval?.let {
|
||||
|
||||
this.request = request.copy(
|
||||
historyInterval = null
|
||||
)
|
||||
|
||||
Pair(
|
||||
Triple(
|
||||
intervalWriteUUID,
|
||||
mutableListOf<Byte>(3).apply {
|
||||
addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList())
|
||||
}.toByteArray()
|
||||
}.toByteArray(),
|
||||
request.copy(
|
||||
historyInterval = null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
uuid = request.saveHistory?.let {
|
||||
|
||||
this.request = request.copy(
|
||||
saveHistory = null
|
||||
)
|
||||
|
||||
Pair(
|
||||
Triple(
|
||||
saveEnabledWriteUUID,
|
||||
mutableListOf<Byte>(4).apply {
|
||||
add(if (it) 1 else 0)
|
||||
}.toByteArray()
|
||||
}.toByteArray(),
|
||||
request.copy(
|
||||
saveHistory = null
|
||||
)
|
||||
)
|
||||
} ?: uuid
|
||||
|
||||
uuid = request.tx?.let {
|
||||
|
||||
this.request = request.copy(
|
||||
tx = null
|
||||
)
|
||||
|
||||
Pair(
|
||||
Triple(
|
||||
txWriteUUID,
|
||||
byteArrayOf(
|
||||
when (it) {
|
||||
|
|
@ -125,6 +119,9 @@ class WriteThermometerCallback(
|
|||
Ble.BleState.TX.PLUS_3 -> 3
|
||||
Ble.BleState.TX.PLUS_4 -> 4
|
||||
}
|
||||
),
|
||||
request.copy(
|
||||
tx = null
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -137,7 +134,7 @@ class WriteThermometerCallback(
|
|||
}?.let {
|
||||
|
||||
gatt.writeCharacteristic(it, uuid.second)
|
||||
|
||||
request = uuid.third
|
||||
return
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
package llc.arma.ble.data
|
||||
|
||||
import android.app.Application
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import llc.arma.ble.R
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
import llc.arma.ble.domain.repository.XlsxRepository
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory
|
||||
import org.apache.poi.util.IOUtils
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.util.Date
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
class XlsxRepositoryImpl @Inject constructor(
|
||||
private val application: Application
|
||||
) : XlsxRepository {
|
||||
|
||||
|
||||
|
||||
override fun exportToXls(data: List<Ble.Accelerometer.HistoryPoint>): File {
|
||||
|
||||
val formatter = SimpleDateFormat("dd.MM.yyyy HH:mm")
|
||||
|
||||
val file = File(application.filesDir, "${UUID.randomUUID()}.xlsx")
|
||||
file.createNewFile()
|
||||
when(data.firstOrNull()){
|
||||
is Ble.Accelerometer.HistoryPoint.Vibration -> {
|
||||
IOUtils.copy(application.resources.openRawResource(R.raw.measure_single_axis), FileOutputStream(file))
|
||||
}
|
||||
else -> {
|
||||
IOUtils.copy(application.resources.openRawResource(R.raw.measure_multiple_axis), FileOutputStream(file))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val fileIn = FileInputStream(file)
|
||||
val workbook = WorkbookFactory.create(fileIn)
|
||||
val worksheet = workbook.getSheetAt(0) as XSSFSheet
|
||||
|
||||
data.withIndex().forEach {
|
||||
|
||||
val row = worksheet.createRow(it.index + 1)
|
||||
val dateX = row.createCell(0)
|
||||
val dateY = row.createCell(2)
|
||||
val dateZ = row.createCell(4)
|
||||
val x = row.createCell(1)
|
||||
val y = row.createCell(3)
|
||||
val z = row.createCell(5)
|
||||
|
||||
when(val value = it.value){
|
||||
is Ble.Accelerometer.HistoryPoint.Angle -> {
|
||||
dateX.setCellValue(formatter.format(Date(value.date)))
|
||||
dateY.setCellValue(formatter.format(Date(value.date)))
|
||||
dateZ.setCellValue(formatter.format(Date(value.date)))
|
||||
x.setCellValue(value.x.toDouble())
|
||||
y.setCellValue(value.y.toDouble())
|
||||
z.setCellValue(value.z.toDouble())
|
||||
}
|
||||
is Ble.Accelerometer.HistoryPoint.Vibration -> {
|
||||
dateX.setCellValue(formatter.format(Date(value.date)))
|
||||
x.setCellValue(value.value.toDouble())
|
||||
}
|
||||
|
||||
is Ble.Accelerometer.HistoryPoint.Accelerate -> {
|
||||
dateX.setCellValue(formatter.format(Date(value.date)))
|
||||
dateY.setCellValue(formatter.format(Date(value.date)))
|
||||
dateZ.setCellValue(formatter.format(Date(value.date)))
|
||||
x.setCellValue(value.x.toDouble())
|
||||
y.setCellValue(value.y.toDouble())
|
||||
z.setCellValue(value.z.toDouble())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fileIn.close()
|
||||
val saveFos = FileOutputStream(file)
|
||||
workbook.write(saveFos)
|
||||
workbook.close()
|
||||
saveFos.close()
|
||||
return file
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -31,18 +31,34 @@ sealed class Ble(
|
|||
val historyInterval: Long?
|
||||
)
|
||||
|
||||
sealed class HistoryPoint {
|
||||
|
||||
class Vibration (
|
||||
val date: Long,
|
||||
val value: Float
|
||||
) : HistoryPoint()
|
||||
|
||||
class Accelerate (
|
||||
val date: Long,
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
val z: Float
|
||||
) : HistoryPoint()
|
||||
|
||||
class Angle (
|
||||
val date: Long,
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
val z: Float
|
||||
) : HistoryPoint()
|
||||
|
||||
}
|
||||
|
||||
class MeasurePoint (
|
||||
val frequency: Long,
|
||||
val value: Float
|
||||
)
|
||||
|
||||
class HistoryPoint (
|
||||
val time: Long,
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
val z: Float
|
||||
)
|
||||
|
||||
data class AccelerometerState(
|
||||
val saveHistory: History,
|
||||
val historyInterval: Long
|
||||
|
|
|
|||
|
|
@ -50,6 +50,6 @@ interface BleRepository {
|
|||
frequency: FftFrequency
|
||||
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>>
|
||||
|
||||
suspend fun getAccelerometerHistoryBySerial(serial: String): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>>
|
||||
suspend fun getAccelerometerHistoryBySerial(serial: String): Flow<Result<ProgressState<List<Ble.Accelerometer.HistoryPoint>>, BleException>>
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package llc.arma.ble.domain.repository
|
||||
|
||||
import java.io.File
|
||||
|
||||
interface EmailRepository {
|
||||
|
||||
fun sendFile(file: File)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package llc.arma.ble.domain.repository
|
||||
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
import llc.arma.ble.domain.usecase.MeasureData
|
||||
import java.io.File
|
||||
|
||||
interface XlsxRepository {
|
||||
|
||||
fun exportToXls(data: List<Ble.Accelerometer.HistoryPoint>): File
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package llc.arma.ble.domain.usecase
|
||||
|
||||
import android.app.Application
|
||||
import llc.arma.ble.R
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
import llc.arma.ble.domain.repository.EmailRepository
|
||||
import llc.arma.ble.domain.repository.XlsxRepository
|
||||
import org.apache.poi.ss.SpreadsheetVersion
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory
|
||||
import org.apache.poi.ss.util.AreaReference
|
||||
import org.apache.poi.ss.util.CellReference
|
||||
import org.apache.poi.util.IOUtils
|
||||
import org.apache.poi.xssf.usermodel.XSSFCell
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet
|
||||
import org.apache.poi.xssf.usermodel.XSSFTable
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
class ExportToXlsx @Inject constructor(
|
||||
private val xlsxRepository: XlsxRepository,
|
||||
private val emailRepository: EmailRepository
|
||||
) {
|
||||
|
||||
operator fun invoke(data: List<Ble.Accelerometer.HistoryPoint>){
|
||||
|
||||
val file = xlsxRepository.exportToXls(data)
|
||||
emailRepository.sendFile(file)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ class GetAccelerometerHistoryBySerial @Inject constructor(
|
|||
private val bleRepository: BleRepository
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(serial: String): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {
|
||||
suspend operator fun invoke(serial: String): Flow<Result<ProgressState<List<Ble.Accelerometer.HistoryPoint>>, BleException>> {
|
||||
|
||||
return bleRepository.getAccelerometerHistoryBySerial(serial)
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<files-path name="xlsx" path="/"/>
|
||||
</paths>
|
||||
Loading…
Reference in New Issue