Экспорт
This commit is contained in:
parent
12df287193
commit
bc569581ad
|
|
@ -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')
|
||||||
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -345,12 +345,11 @@ fun Display(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Angle(
|
public fun Angle(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
angle: Float
|
angle: Float
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -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,25 +193,174 @@ fun Display(
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
val lineChart = lineChart()
|
val xProducer = remember {
|
||||||
|
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||||
|
}
|
||||||
|
|
||||||
Chart(
|
val yProducer = remember {
|
||||||
chart = lineChart,
|
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||||
chartModelProducer = producer,
|
}
|
||||||
startAxis = startAxis(),
|
|
||||||
bottomAxis = bottomAxis(
|
val zProducer = remember {
|
||||||
tickLength = 0.dp,
|
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||||
valueFormatter = axisValueFormatter,
|
}
|
||||||
labelRotationDegrees = -90f,
|
|
||||||
),
|
xProducer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
|
||||||
modifier = Modifier.fillMaxSize(),
|
when(measurePoint){
|
||||||
chartScrollSpec = rememberChartScrollSpec(
|
is Ble.Accelerometer.HistoryPoint.Accelerate -> {
|
||||||
initialScroll = InitialScroll.End,
|
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.x )
|
||||||
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
}
|
||||||
autoScrollAnimationSpec = tween(0)
|
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 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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 мин."
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 мин."
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
x: Float,
|
||||||
y: Float
|
y: Float
|
||||||
): 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.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,11 +220,37 @@ class ReadAccelerometerHistoryCallback(
|
||||||
onResult(
|
onResult(
|
||||||
Result.success(
|
Result.success(
|
||||||
ProgressState.Finished(
|
ProgressState.Finished(
|
||||||
resultTemperaturePackage.withIndex().map {
|
when(mode){
|
||||||
Ble.Accelerometer.MeasurePoint(
|
AccelViewMode.ACCELERATION,
|
||||||
frequency = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
|
AccelViewMode.PEAK_ACCELERATION,
|
||||||
value = (it.value * scale.k) / Short.MAX_VALUE
|
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(
|
onResult(
|
||||||
Result.success(
|
Result.success(
|
||||||
ProgressState.Finished(
|
ProgressState.Finished(
|
||||||
resultTemperaturePackage.withIndex().map {
|
when(mode){
|
||||||
Ble.Accelerometer.MeasurePoint(
|
AccelViewMode.ACCELERATION,
|
||||||
frequency = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
|
AccelViewMode.PEAK_ACCELERATION,
|
||||||
value = (it.value * scale.k) / Short.MAX_VALUE
|
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
|
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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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?
|
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 (
|
class MeasurePoint (
|
||||||
val frequency: Long,
|
val frequency: Long,
|
||||||
val value: Float
|
val value: Float
|
||||||
)
|
)
|
||||||
|
|
||||||
class HistoryPoint (
|
|
||||||
val time: Long,
|
|
||||||
val x: Float,
|
|
||||||
val y: Float,
|
|
||||||
val z: Float
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AccelerometerState(
|
data class AccelerometerState(
|
||||||
val saveHistory: History,
|
val saveHistory: History,
|
||||||
val historyInterval: Long
|
val historyInterval: Long
|
||||||
|
|
|
||||||
|
|
@ -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>>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
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.
|
|
@ -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