Экспорт

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 {
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')
}

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 мин."
)
}

View File

@ -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 мин."
)
}

View File

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

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,
y: 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.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
)
}
}
}
)
)

View File

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

View File

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

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

View File

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

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
) {
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.

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>