mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-21 16:34:23 +01:00
Add connecting view to profiles
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
package no.nordicsemi.android.hts
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package no.nordicsemi.android.hts.data
|
||||
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -11,6 +16,12 @@ internal class HTSRepository @Inject constructor() {
|
||||
private val _data = MutableStateFlow(HTSData())
|
||||
val data: StateFlow<HTSData> = _data
|
||||
|
||||
private val _command = MutableSharedFlow<DisconnectCommand>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
|
||||
val command = _command.asSharedFlow()
|
||||
|
||||
private val _status = MutableStateFlow(BleManagerStatus.CONNECTING)
|
||||
val status = _status.asStateFlow()
|
||||
|
||||
fun setNewTemperature(temperature: Float) {
|
||||
_data.tryEmit(_data.value.copy(temperatureValue = temperature))
|
||||
}
|
||||
@@ -23,7 +34,16 @@ internal class HTSRepository @Inject constructor() {
|
||||
_data.tryEmit(_data.value.copy(temperatureUnit = unit))
|
||||
}
|
||||
|
||||
fun sendDisconnectCommand() {
|
||||
_command.tryEmit(DisconnectCommand)
|
||||
}
|
||||
|
||||
fun setNewStatus(status: BleManagerStatus) {
|
||||
_status.value = status
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
_status.value = BleManagerStatus.CONNECTING
|
||||
_data.tryEmit(HTSData())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package no.nordicsemi.android.hts.data
|
||||
|
||||
internal object DisconnectCommand
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Nordic Semiconductor
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package no.nordicsemi.android.hts.repository
|
||||
|
||||
import no.nordicsemi.android.ble.common.callback.DateTimeDataCallback
|
||||
import no.nordicsemi.android.ble.data.Data
|
||||
import java.util.*
|
||||
|
||||
internal object HTSDateTimeParser {
|
||||
/**
|
||||
* Parses the date and time info.
|
||||
*
|
||||
* @param data
|
||||
* @return time in human readable format
|
||||
*/
|
||||
fun parse(data: Data): String {
|
||||
return parse(data, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the date and time info. This data has 7 bytes
|
||||
*
|
||||
* @param data
|
||||
* @param offset
|
||||
* offset to start reading the time
|
||||
* @return time in human readable format
|
||||
*/
|
||||
/* package */
|
||||
@JvmStatic
|
||||
fun parse(data: Data, offset: Int): String {
|
||||
val calendar = DateTimeDataCallback.readDateTime(data, offset)
|
||||
return String.format(Locale.US, "%1\$te %1\$tb %1\$tY, %1\$tH:%1\$tM:%1\$tS", calendar)
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,7 @@ import android.content.Context
|
||||
import no.nordicsemi.android.ble.common.callback.ht.TemperatureMeasurementDataCallback
|
||||
import no.nordicsemi.android.ble.common.profile.ht.TemperatureType
|
||||
import no.nordicsemi.android.ble.common.profile.ht.TemperatureUnit
|
||||
import no.nordicsemi.android.ble.data.Data
|
||||
import no.nordicsemi.android.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.log.LogContract
|
||||
import no.nordicsemi.android.service.BatteryManager
|
||||
import java.util.*
|
||||
|
||||
@@ -50,13 +48,6 @@ internal class HTSManager internal constructor(
|
||||
private var htCharacteristic: BluetoothGattCharacteristic? = null
|
||||
|
||||
private val temperatureMeasurementDataCallback = object : TemperatureMeasurementDataCallback() {
|
||||
override fun onDataReceived(device: BluetoothDevice, data: Data) {
|
||||
log(
|
||||
LogContract.Log.Level.APPLICATION,
|
||||
"\"" + HTSTemperatureMeasurementParser.parse(data) + "\" received"
|
||||
)
|
||||
super.onDataReceived(device, data)
|
||||
}
|
||||
|
||||
override fun onTemperatureMeasurementReceived(
|
||||
device: BluetoothDevice,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package no.nordicsemi.android.hts.repository
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.service.ForegroundBleService
|
||||
import javax.inject.Inject
|
||||
@@ -9,7 +11,19 @@ import javax.inject.Inject
|
||||
internal class HTSService : ForegroundBleService() {
|
||||
|
||||
@Inject
|
||||
lateinit var dataHolder: HTSRepository
|
||||
lateinit var repository: HTSRepository
|
||||
|
||||
override val manager: HTSManager by lazy { HTSManager(this, dataHolder) }
|
||||
override val manager: HTSManager by lazy { HTSManager(this, repository) }
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
status.onEach {
|
||||
repository.setNewStatus(it)
|
||||
}.launchIn(scope)
|
||||
|
||||
repository.command.onEach {
|
||||
stopSelf()
|
||||
}.launchIn(scope)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Nordic Semiconductor
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package no.nordicsemi.android.hts.repository
|
||||
|
||||
import no.nordicsemi.android.ble.data.Data
|
||||
import java.util.*
|
||||
|
||||
private const val TEMPERATURE_UNIT_FLAG: Byte = 0x01 // 1 bit
|
||||
private const val TIMESTAMP_FLAG: Byte = 0x02 // 1 bits
|
||||
private const val TEMPERATURE_TYPE_FLAG: Byte = 0x04 // 1 bit
|
||||
|
||||
internal object HTSTemperatureMeasurementParser {
|
||||
|
||||
fun parse(data: Data): String {
|
||||
var offset = 0
|
||||
val flags = data.getIntValue(Data.FORMAT_UINT8, offset++)!!
|
||||
|
||||
/*
|
||||
* false Temperature is in Celsius degrees
|
||||
* true Temperature is in Fahrenheit degrees
|
||||
*/
|
||||
val fahrenheit = flags and TEMPERATURE_UNIT_FLAG.toInt() > 0
|
||||
|
||||
/*
|
||||
* false No Timestamp in the packet
|
||||
* true There is a timestamp information
|
||||
*/
|
||||
val timestampIncluded = flags and TIMESTAMP_FLAG.toInt() > 0
|
||||
|
||||
/*
|
||||
* false Temperature type is not included
|
||||
* true Temperature type included in the packet
|
||||
*/
|
||||
val temperatureTypeIncluded = flags and TEMPERATURE_TYPE_FLAG.toInt() > 0
|
||||
val tempValue = data.getFloatValue(Data.FORMAT_FLOAT, offset)!!
|
||||
offset += 4
|
||||
var dateTime: String? = null
|
||||
if (timestampIncluded) {
|
||||
dateTime = HTSDateTimeParser.parse(data, offset)
|
||||
offset += 7
|
||||
}
|
||||
var type: String? = null
|
||||
if (temperatureTypeIncluded) {
|
||||
type = HTSTemperatureTypeParser.parse(data, offset)
|
||||
// offset++;
|
||||
}
|
||||
val builder = StringBuilder()
|
||||
builder.append(String.format(Locale.US, "%.02f", tempValue))
|
||||
if (fahrenheit) builder.append("°F") else builder.append("°C")
|
||||
if (timestampIncluded) builder.append("\nTime: ").append(dateTime)
|
||||
if (temperatureTypeIncluded) builder.append("\nType: ").append(type)
|
||||
return builder.toString()
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Nordic Semiconductor
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package no.nordicsemi.android.hts.repository
|
||||
|
||||
import no.nordicsemi.android.ble.data.Data
|
||||
|
||||
internal object HTSTemperatureTypeParser {
|
||||
fun parse(data: Data): String {
|
||||
return parse(data, 0)
|
||||
}
|
||||
|
||||
/* package */
|
||||
@JvmStatic
|
||||
fun parse(data: Data, offset: Int): String {
|
||||
return when (data.value!![offset].toInt()) {
|
||||
1 -> "Armpit"
|
||||
2 -> "Body (general)"
|
||||
3 -> "Ear (usually ear lobe)"
|
||||
4 -> "Finger"
|
||||
5 -> "Gastro-intestinal Tract"
|
||||
6 -> "Mouth"
|
||||
7 -> "Rectum"
|
||||
8 -> "Toe"
|
||||
9 -> "Tympanum (ear drum)"
|
||||
else -> "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package no.nordicsemi.android.hts.view
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@@ -9,46 +9,40 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import no.nordicsemi.android.hts.R
|
||||
import no.nordicsemi.android.hts.data.HTSData
|
||||
import no.nordicsemi.android.hts.repository.HTSService
|
||||
import no.nordicsemi.android.hts.viewmodel.HTSViewModel
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.utils.isServiceRunning
|
||||
import no.nordicsemi.android.theme.view.DeviceConnectingView
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
|
||||
@Composable
|
||||
fun HTSScreen(finishAction: () -> Unit) {
|
||||
val viewModel: HTSViewModel = hiltViewModel()
|
||||
val state = viewModel.state.collectAsState().value
|
||||
val isActive = viewModel.isActive.collectAsState().value
|
||||
|
||||
val context = LocalContext.current
|
||||
LaunchedEffect(isActive) {
|
||||
if (!isActive) {
|
||||
finishAction()
|
||||
}
|
||||
if (context.isServiceRunning(HTSService::class.java.name)) {
|
||||
val intent = Intent(context, HTSService::class.java)
|
||||
context.stopService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect("start-service") {
|
||||
if (!context.isServiceRunning(HTSService::class.java.name)) {
|
||||
LaunchedEffect(state.isActive) {
|
||||
if (state.isActive) {
|
||||
val intent = Intent(context, HTSService::class.java)
|
||||
context.startService(intent)
|
||||
} else {
|
||||
finishAction()
|
||||
}
|
||||
}
|
||||
|
||||
HTSView(state) { viewModel.onEvent(it) }
|
||||
HTSView(state.viewState) { viewModel.onEvent(it) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HTSView(state: HTSData, onEvent: (HTSScreenViewEvent) -> Unit) {
|
||||
private fun HTSView(state: HTSViewState, onEvent: (HTSScreenViewEvent) -> Unit) {
|
||||
Column {
|
||||
BackIconAppBar(stringResource(id = R.string.hts_title)) {
|
||||
onEvent(DisconnectEvent)
|
||||
}
|
||||
|
||||
HTSContentView(state) { onEvent(it) }
|
||||
when (state) {
|
||||
is DisplayDataState -> HTSContentView(state.data) { onEvent(it) }
|
||||
LoadingState -> DeviceConnectingView()
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package no.nordicsemi.android.hts.view
|
||||
|
||||
import no.nordicsemi.android.hts.data.HTSData
|
||||
|
||||
internal data class HTSState(
|
||||
val viewState: HTSViewState,
|
||||
val isActive: Boolean = true
|
||||
)
|
||||
|
||||
internal sealed class HTSViewState
|
||||
|
||||
internal object LoadingState : HTSViewState()
|
||||
|
||||
internal data class DisplayDataState(val data: HTSData) : HTSViewState()
|
||||
@@ -1,20 +1,34 @@
|
||||
package no.nordicsemi.android.hts.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import no.nordicsemi.android.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.hts.view.DisconnectEvent
|
||||
import no.nordicsemi.android.hts.view.DisplayDataState
|
||||
import no.nordicsemi.android.hts.view.HTSScreenViewEvent
|
||||
import no.nordicsemi.android.hts.view.HTSState
|
||||
import no.nordicsemi.android.hts.view.LoadingState
|
||||
import no.nordicsemi.android.hts.view.OnTemperatureUnitSelected
|
||||
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class HTSViewModel @Inject constructor(
|
||||
private val dataHolder: HTSRepository
|
||||
) : CloseableViewModel() {
|
||||
private val repository: HTSRepository
|
||||
) : ViewModel() {
|
||||
|
||||
val state = dataHolder.data
|
||||
val state = repository.data.combine(repository.status) { data, status ->
|
||||
when (status) {
|
||||
BleManagerStatus.CONNECTING -> HTSState(LoadingState)
|
||||
BleManagerStatus.OK -> HTSState(DisplayDataState(data))
|
||||
BleManagerStatus.DISCONNECTED -> HTSState(DisplayDataState(data), false)
|
||||
}
|
||||
}.stateIn(viewModelScope, SharingStarted.Lazily, HTSState(LoadingState))
|
||||
|
||||
fun onEvent(event: HTSScreenViewEvent) {
|
||||
when (event) {
|
||||
@@ -24,11 +38,16 @@ internal class HTSViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun onDisconnectButtonClick() {
|
||||
finish()
|
||||
dataHolder.clear()
|
||||
repository.sendDisconnectCommand()
|
||||
repository.clear()
|
||||
}
|
||||
|
||||
private fun onTemperatureUnitSelected(event: OnTemperatureUnitSelected) {
|
||||
dataHolder.setTemperatureUnit(event.value)
|
||||
repository.setTemperatureUnit(event.value)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
repository.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package no.nordicsemi.android.hts
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user