Экспорт

This commit is contained in:
Vineyro 2023-10-20 17:02:07 +07:00
parent 12df287193
commit bc569581ad
28 changed files with 605 additions and 103 deletions

View File

@ -11,10 +11,10 @@ android {
defaultConfig { defaultConfig {
applicationId "llc.arma.ble" applicationId "llc.arma.ble"
minSdk 24 minSdk 26
targetSdk 33 targetSdk 33
versionCode 9 versionCode 10
versionName "1.2.9" versionName "1.2.10"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@ -87,4 +87,6 @@ dependencies {
implementation "com.patrykandpatrick.vico:compose:1.7.1" implementation "com.patrykandpatrick.vico:compose:1.7.1"
implementation "com.patrykandpatrick.vico:compose-m3:1.7.1" implementation "com.patrykandpatrick.vico:compose-m3:1.7.1"
implementation files('libs/poishadow-all.jar')
} }

BIN
app/libs/poishadow-all.jar Normal file

Binary file not shown.

View File

@ -29,6 +29,18 @@
tools:targetApi="31" tools:targetApi="31"
android:name=".app.framework.App"> 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 <activity
android:name=".app.ui.MainActivity" android:name=".app.ui.MainActivity"
android:exported="true" android:exported="true"

View File

@ -5,4 +5,18 @@ import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp() @HiltAndroidApp()
class App : Application() { 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");
}
} }

View File

@ -5,7 +5,11 @@ import dagger.Module
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import llc.arma.ble.data.BleRepositoryImpl 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.BleRepository
import llc.arma.ble.domain.repository.EmailRepository
import llc.arma.ble.domain.repository.XlsxRepository
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
@ -14,4 +18,10 @@ interface RepositoryBinding {
@Binds @Binds
fun bindBleRepository(bleRepositoryImpl: BleRepositoryImpl): BleRepository fun bindBleRepository(bleRepositoryImpl: BleRepositoryImpl): BleRepository
@Binds
fun bindEmailRepository(repository: EmailRepositoryImpl): EmailRepository
@Binds
fun bindXlsxRepository(repository: XlsxRepositoryImpl): XlsxRepository
} }

View File

@ -7,14 +7,17 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import llc.arma.ble.app.ui.common.BaseViewModel 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.GetBleAroundFlow
import llc.arma.ble.domain.usecase.GetConnectedBleDevices import llc.arma.ble.domain.usecase.GetConnectedBleDevices
import llc.arma.ble.domain.usecase.MeasureData
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class BleListViewModel @Inject constructor( class BleListViewModel @Inject constructor(
getBleAroundFlow: GetBleAroundFlow, getBleAroundFlow: GetBleAroundFlow,
getConnectedBleDevices: GetConnectedBleDevices getConnectedBleDevices: GetConnectedBleDevices,
exportToXlsx: ExportToXlsx
) : BaseViewModel<BleListContract.State, BleListContract.Event, BleListContract.Effect>() { ) : BaseViewModel<BleListContract.State, BleListContract.Event, BleListContract.Effect>() {
init { init {

View File

@ -226,6 +226,7 @@ fun Display(
producer.setEntries( producer.setEntries(
data.mapIndexed { index, measurePoint -> data.mapIndexed { index, measurePoint ->
AccelerometerEntry(measurePoint.frequency, index.toFloat(), measurePoint.value) AccelerometerEntry(measurePoint.frequency, index.toFloat(), measurePoint.value)
} }
) )
} }

View File

@ -345,12 +345,11 @@ fun Display(
} }
} }
} }
@Composable @Composable
private fun Angle( public fun Angle(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
angle: Float angle: Float
) { ) {

View File

@ -23,6 +23,7 @@ import llc.arma.ble.app.ui.common.ViewState
import llc.arma.ble.domain.model.BleInfo import llc.arma.ble.domain.model.BleInfo
import javax.inject.Inject import javax.inject.Inject
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.CloudUpload
import androidx.compose.material.icons.rounded.Refresh import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.style.TextAlign 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.compose.chart.scroll.rememberChartScrollSpec
import com.patrykandpatrick.vico.core.axis.AxisPosition import com.patrykandpatrick.vico.core.axis.AxisPosition
import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter 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.ChartEntry
import com.patrykandpatrick.vico.core.entry.FloatEntry import com.patrykandpatrick.vico.core.entry.FloatEntry
import com.patrykandpatrick.vico.core.scroll.AutoScrollCondition 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.model.Ble
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.ExportToXlsx
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 llc.arma.ble.domain.usecase.GetAccelerometerHistoryBySerial import llc.arma.ble.domain.usecase.GetAccelerometerHistoryBySerial
import llc.arma.ble.domain.usecase.MeasureData
import java.util.Date import java.util.Date
class AccelEntry( class AccelEntry(
@ -108,6 +113,21 @@ fun AccelerometerHistory(
style = MaterialTheme.typography.titleLarge 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( IconButton(
onClick = { onClick = {
viewModel.setEvent(AccelerometerHistoryContract.Event.OnRefreshHistory(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency)) viewModel.setEvent(AccelerometerHistoryContract.Event.OnRefreshHistory(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
@ -164,14 +184,6 @@ fun Display(
} else { } else {
val producer = remember {
ChartEntryModelProducer(listOf<FloatEntry>())
}
producer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
AccelEntry(measurePoint.frequency, index.toFloat(), measurePoint.value )
})
val axisValueFormatter = val axisValueFormatter =
AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues -> AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
(chartValues.chartEntryModel.entries.firstOrNull() (chartValues.chartEntryModel.entries.firstOrNull()
@ -181,18 +193,90 @@ fun Display(
.orEmpty() .orEmpty()
} }
val lineChart = lineChart() val xProducer = remember {
ChartEntryModelProducer(listOf<FloatEntry>())
}
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(
chart = lineChart, chart = lineChart,
chartModelProducer = producer, chartModelProducer = xProducer,
startAxis = startAxis(), startAxis = startAxis(),
bottomAxis = bottomAxis( bottomAxis = bottomAxis(
tickLength = 0.dp, tickLength = 0.dp,
valueFormatter = axisValueFormatter, valueFormatter = axisValueFormatter,
labelRotationDegrees = -90f, labelRotationDegrees = -90f,
), ),
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxWidth()
.weight(1f),
autoScaleUp = AutoScaleUp.None,
diffAnimationSpec = tween(0),
chartScrollSpec = rememberChartScrollSpec( chartScrollSpec = rememberChartScrollSpec(
initialScroll = InitialScroll.End, initialScroll = InitialScroll.End,
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased, autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
@ -200,6 +284,83 @@ fun Display(
) )
) )
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 StopMeasure : Event()
object OnExport : Event()
data class OnStart( data class OnStart(
val serial: String, val serial: String,
val accelScale: AccelScale, val accelScale: AccelScale,
@ -284,7 +447,7 @@ class AccelerometerHistoryContract {
sealed class State : ViewState { sealed class State : ViewState {
data class Display( data class Display(
val loadingHistoryState : ProgressState<List<Ble.Accelerometer.MeasurePoint>> val loadingHistoryState : ProgressState<List<Ble.Accelerometer.HistoryPoint>>
) : State() ) : State()
object Exception : State() object Exception : State()
@ -302,6 +465,7 @@ class AccelerometerHistoryContract {
@HiltViewModel @HiltViewModel
class AccelerometerHistoryViewModel @Inject constructor( class AccelerometerHistoryViewModel @Inject constructor(
private val getAccelerometerHistoryBySerial: GetAccelerometerHistoryBySerial, private val getAccelerometerHistoryBySerial: GetAccelerometerHistoryBySerial,
private val exportToXlsx: ExportToXlsx
) : BaseViewModel<AccelerometerHistoryContract.State, AccelerometerHistoryContract.Event, AccelerometerHistoryContract.Effect>() { ) : BaseViewModel<AccelerometerHistoryContract.State, AccelerometerHistoryContract.Event, AccelerometerHistoryContract.Effect>() {
var measureJob: Job? = null var measureJob: Job? = null
@ -317,15 +481,28 @@ class AccelerometerHistoryViewModel @Inject constructor(
is AccelerometerHistoryContract.Event.OnStart -> reduce(viewState.value, event) is AccelerometerHistoryContract.Event.OnStart -> reduce(viewState.value, event)
is AccelerometerHistoryContract.Event.OnRefreshHistory -> reduce(viewState.value, event) is AccelerometerHistoryContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
is AccelerometerHistoryContract.Event.StopMeasure -> 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( private fun reduce(
state: AccelerometerHistoryContract.State, state: AccelerometerHistoryContract.State,
event: AccelerometerHistoryContract.Event.StopMeasure event: AccelerometerHistoryContract.Event.StopMeasure
) { ) {
measureJob?.cancel() measureJob?.cancel()
measureJob = null measureJob = null

View File

@ -22,7 +22,7 @@ fun IntervalEdit(
){ ){
var value by remember(state.accelerometerState.historyInterval) { 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 val maxInterval = 10 * 24 * 60 * 60 * 1000

View File

@ -142,13 +142,17 @@ fun Write(
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) { ) {
val hours = it / 1000 / 60 / 60
val minutes = (it - ( hours * 1000 * 60 * 60 )) / 1000 / 60
Text( Text(
text = "Интервал измерний" text = "Интервал измерений"
) )
Text( Text(
color = MaterialTheme.colorScheme.secondary, color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
text = "${it / 1000 / 60 / 60} ч." text = "$hours ч. $minutes мин."
) )
} }

View File

@ -137,13 +137,17 @@ fun Write(
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) { ) {
val hours = it / 1000 / 60 / 60
val minutes = (it - ( hours * 1000 * 60 * 60 )) / 1000 / 60
Text( Text(
text = "Интервал измерний" text = "Интервал измерений"
) )
Text( Text(
color = MaterialTheme.colorScheme.secondary, color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
text = "${it / 1000 / 60 / 60} ч." text = "$hours ч. $minutes мин."
) )
} }

View File

@ -542,7 +542,6 @@ class BleRepositoryImpl @Inject constructor(
serviceId = serviceUUID, serviceId = serviceUUID,
characteristicId = accelerometerReadUUID, characteristicId = accelerometerReadUUID,
).fold( ).fold(
onSuccess = { onSuccess = {
Log.d("history", it.joinToString { it.toString() }) Log.d("history", it.joinToString { it.toString() })
val scale = when(it[1].toInt()){ val scale = when(it[1].toInt()){
@ -559,6 +558,7 @@ class BleRepositoryImpl @Inject constructor(
1 -> AccelViewMode.PEAK_ACCELERATION 1 -> AccelViewMode.PEAK_ACCELERATION
2 -> AccelViewMode.RMS 2 -> AccelViewMode.RMS
3 -> AccelViewMode.VIBRATION 3 -> AccelViewMode.VIBRATION
4 -> AccelViewMode.ANGLE
else -> { else -> {
return Result.failure(BleException.UnexpectedResponse) return Result.failure(BleException.UnexpectedResponse)
} }
@ -572,7 +572,6 @@ class BleRepositoryImpl @Inject constructor(
onFailure = { onFailure = {
return Result.failure(BleException.UnexpectedResponse) return Result.failure(BleException.UnexpectedResponse)
} }
) )
} }
false -> Ble.Accelerometer.History.Disabled false -> Ble.Accelerometer.History.Disabled
@ -738,17 +737,31 @@ class BleRepositoryImpl @Inject constructor(
override suspend fun getAccelerometerHistoryBySerial( override suspend fun getAccelerometerHistoryBySerial(
serial: String serial: String
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> { ): Flow<Result<ProgressState<List<Ble.Accelerometer.HistoryPoint>>, BleException>> {
var gatt: BluetoothGatt? = null var gatt: BluetoothGatt? = null
return callbackFlow { return callbackFlow {
deviceCache[serial]?.device?.let { device -> deviceCache[serial]?.let { result ->
val device = result.device
if (checkPermission()) { if (checkPermission()) {
val scale = writeCharacteristic( val state = readAccelState(result).fold(
onSuccess = {
},
onFailure = {
null
}
)
var scale: AccelScale? = null
var mode: AccelViewMode? = null
writeCharacteristic(
device = device, device = device,
serviceId = serviceUUID, serviceId = serviceUUID,
characteristicId = accelerometerReadUUID, characteristicId = accelerometerReadUUID,
@ -761,13 +774,21 @@ class BleRepositoryImpl @Inject constructor(
characteristicId = accelerometerReadUUID, characteristicId = accelerometerReadUUID,
).fold( ).fold(
onSuccess = { onSuccess = {
when(it[1].toInt()){ scale = when(it[1].toInt()){
0 -> AccelScale.S_2 0 -> AccelScale.S_2
1 -> AccelScale.S_4 1 -> AccelScale.S_4
2 -> AccelScale.S_8 2 -> AccelScale.S_8
3 -> AccelScale.S_16 3 -> AccelScale.S_16
else -> null 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 } onFailure = { null }
) )
@ -777,12 +798,12 @@ class BleRepositoryImpl @Inject constructor(
} }
) )
if(scale != null) { if(scale != null && mode != null) {
gatt = device.connectGatt( gatt = device.connectGatt(
app, app,
false, false,
ReadAccelerometerHistoryCallback(scale, app) { ReadAccelerometerHistoryCallback(mode!!, scale!!, app) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
send(it) send(it)
} }

View File

@ -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
}
)
}
}

View File

@ -261,7 +261,7 @@ fun calculateAngle(
} }
fun calculateZAngle( public fun calculateZAngle(
x: Float, x: Float,
y: Float y: Float
): Float { ): Float {

View File

@ -14,11 +14,13 @@ import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState import llc.arma.ble.domain.common.ProgressState
import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.usecase.AccelScale import llc.arma.ble.domain.usecase.AccelScale
import llc.arma.ble.domain.usecase.AccelViewMode
class ReadAccelerometerHistoryCallback( class ReadAccelerometerHistoryCallback(
private val mode: AccelViewMode,
private val scale: AccelScale, private val scale: AccelScale,
private val app: Application, 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() { ) : BluetoothGattCallback() {
enum class Property { enum class Property {
@ -218,12 +220,38 @@ class ReadAccelerometerHistoryCallback(
onResult( onResult(
Result.success( Result.success(
ProgressState.Finished( 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 { resultTemperaturePackage.withIndex().map {
Ble.Accelerometer.MeasurePoint( Ble.Accelerometer.HistoryPoint.Vibration(
frequency = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!), date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
value = (it.value * scale.k) / Short.MAX_VALUE value = (it.value * scale.k) / Short.MAX_VALUE
) )
} }
}
}
) )
) )
) )
@ -261,12 +289,38 @@ class ReadAccelerometerHistoryCallback(
onResult( onResult(
Result.success( Result.success(
ProgressState.Finished( 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 { resultTemperaturePackage.withIndex().map {
Ble.Accelerometer.MeasurePoint( Ble.Accelerometer.HistoryPoint.Vibration(
frequency = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!), date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
value = (it.value * scale.k) / Short.MAX_VALUE value = (it.value * scale.k) / Short.MAX_VALUE
) )
} }
}
}
) )
) )
) )

View File

@ -64,36 +64,33 @@ class WriteAccelerometerCallback(
status: Int 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 = fun UInt.to4ByteArrayInLittleEndian(): ByteArray =
(3 downTo 0).map { (3 downTo 0).map {
(this shr (it * Byte.SIZE_BITS)).toByte() (this shr (it * Byte.SIZE_BITS)).toByte()
}.toByteArray() }.toByteArray()
var uuid: Pair<UUID, ByteArray>? = null var uuid: Triple<UUID, ByteArray, Ble.Accelerometer.WriteRequest>? = null
uuid = request.historyInterval?.let { uuid = request.historyInterval?.let {
this.request = request.copy( Triple(
historyInterval = null
)
Pair(
intervalWriteUUID, intervalWriteUUID,
mutableListOf<Byte>(3).apply { mutableListOf<Byte>(3).apply {
addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList()) addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList())
}.toByteArray() }.toByteArray(),
request.copy(
historyInterval = null
)
) )
} }
uuid = request.saveHistory?.let { uuid = request.saveHistory?.let {
this.request = request.copy( Triple(
saveHistory = null
)
Pair(
saveEnabledWriteUUID, saveEnabledWriteUUID,
mutableListOf<Byte>(4).apply { mutableListOf<Byte>(4).apply {
add(if (it is Ble.Accelerometer.History.Enabled) 1 else 0) add(if (it is Ble.Accelerometer.History.Enabled) 1 else 0)
@ -101,17 +98,16 @@ class WriteAccelerometerCallback(
add(it.mode.sendData) add(it.mode.sendData)
add(it.scale.sendData) add(it.scale.sendData)
} }
}.toByteArray() }.toByteArray(),
request.copy(
saveHistory = null
)
) )
} ?: uuid } ?: uuid
uuid = request.tx?.let { uuid = request.tx?.let {
this.request = request.copy( Triple(
tx = null
)
Pair(
txWriteUUID, txWriteUUID,
byteArrayOf( byteArrayOf(
when (it) { when (it) {
@ -125,6 +121,9 @@ class WriteAccelerometerCallback(
Ble.BleState.TX.PLUS_3 -> 3 Ble.BleState.TX.PLUS_3 -> 3
Ble.BleState.TX.PLUS_4 -> 4 Ble.BleState.TX.PLUS_4 -> 4
} }
),
request.copy(
tx = null
) )
) )
@ -138,6 +137,8 @@ class WriteAccelerometerCallback(
gatt.writeCharacteristic(it, uuid.second) gatt.writeCharacteristic(it, uuid.second)
request = uuid.third
return return
} }

View File

@ -75,43 +75,37 @@ class WriteThermometerCallback(
(this shr (it * Byte.SIZE_BITS)).toByte() (this shr (it * Byte.SIZE_BITS)).toByte()
}.toByteArray() }.toByteArray()
var uuid: Pair<UUID, ByteArray>? = null var uuid: Triple<UUID, ByteArray, Ble.Thermometer.WriteRequest>? = null
uuid = request.historyInterval?.let { uuid = request.historyInterval?.let {
this.request = request.copy( Triple(
historyInterval = null
)
Pair(
intervalWriteUUID, intervalWriteUUID,
mutableListOf<Byte>(3).apply { mutableListOf<Byte>(3).apply {
addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList()) addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList())
}.toByteArray() }.toByteArray(),
request.copy(
historyInterval = null
)
) )
} }
uuid = request.saveHistory?.let { uuid = request.saveHistory?.let {
this.request = request.copy( Triple(
saveHistory = null
)
Pair(
saveEnabledWriteUUID, saveEnabledWriteUUID,
mutableListOf<Byte>(4).apply { mutableListOf<Byte>(4).apply {
add(if (it) 1 else 0) add(if (it) 1 else 0)
}.toByteArray() }.toByteArray(),
request.copy(
saveHistory = null
)
) )
} ?: uuid } ?: uuid
uuid = request.tx?.let { uuid = request.tx?.let {
this.request = request.copy( Triple(
tx = null
)
Pair(
txWriteUUID, txWriteUUID,
byteArrayOf( byteArrayOf(
when (it) { when (it) {
@ -125,6 +119,9 @@ class WriteThermometerCallback(
Ble.BleState.TX.PLUS_3 -> 3 Ble.BleState.TX.PLUS_3 -> 3
Ble.BleState.TX.PLUS_4 -> 4 Ble.BleState.TX.PLUS_4 -> 4
} }
),
request.copy(
tx = null
) )
) )
@ -137,7 +134,7 @@ class WriteThermometerCallback(
}?.let { }?.let {
gatt.writeCharacteristic(it, uuid.second) gatt.writeCharacteristic(it, uuid.second)
request = uuid.third
return return
} }

View File

@ -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
}
}

View File

@ -31,16 +31,32 @@ sealed class Ble(
val historyInterval: Long? val historyInterval: Long?
) )
class MeasurePoint ( sealed class HistoryPoint {
val frequency: Long,
val value: Float
)
class HistoryPoint ( class Vibration (
val time: Long, val date: Long,
val value: Float
) : HistoryPoint()
class Accelerate (
val date: Long,
val x: Float, val x: Float,
val y: Float, val y: Float,
val z: 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
) )
data class AccelerometerState( data class AccelerometerState(

View File

@ -50,6 +50,6 @@ interface BleRepository {
frequency: FftFrequency frequency: FftFrequency
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> ): 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>>
} }

View File

@ -0,0 +1,9 @@
package llc.arma.ble.domain.repository
import java.io.File
interface EmailRepository {
fun sendFile(file: File)
}

View 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
}

View 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)
}
}

View File

@ -12,7 +12,7 @@ class GetAccelerometerHistoryBySerial @Inject constructor(
private val bleRepository: BleRepository 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) return bleRepository.getAccelerometerHistoryBySerial(serial)

Binary file not shown.

Binary file not shown.

View File

@ -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>