diff --git a/app/build.gradle b/app/build.gradle index d6e5663..2081129 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,6 +46,7 @@ android { buildFeatures { dataBinding true } + buildTypes { release { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 57c24e9..4e663b6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,12 +12,8 @@ - - + + diff --git a/app/src/main/java/Constants.kt b/app/src/main/java/Constants.kt index b23dc50..89c6cb0 100644 --- a/app/src/main/java/Constants.kt +++ b/app/src/main/java/Constants.kt @@ -1,12 +1,3 @@ -/** - * Device Types (internal unique strings used to classify which device was used for a given experiment/scan) - */ -const val DEVICE_TYPE_LS1 = "LinkSquare" - -const val DEVICE_TYPE_NIR = "LinkSquareNIR" - -const val DEVICE_TYPE_NANO = "InnoSpectra Nano" - const val packageId = "org.phenoapps.prospector" /*** @@ -46,6 +37,8 @@ const val DEVICE_LINK_SQUARE = "$packageId.DEVICE_TYPE_LINK_SQUARE" const val DEVICE_INNO_SPECTRA = "$packageId.DEVICE_TYPE_INNO_SPECTRA" +const val DEVICE_INDIGO = "$packageId.DEVICE_TYPE_INDIGO" + const val EXPORT_TYPE = "$packageId.EXPORT_TYPE" const val DEVICE_INFO = "$packageId.DEVICE_INFO" diff --git a/app/src/main/java/org/phenoapps/prospector/activities/MainActivity.kt b/app/src/main/java/org/phenoapps/prospector/activities/MainActivity.kt index b710204..abdce62 100644 --- a/app/src/main/java/org/phenoapps/prospector/activities/MainActivity.kt +++ b/app/src/main/java/org/phenoapps/prospector/activities/MainActivity.kt @@ -5,25 +5,18 @@ import ALPHA_DESC import BULB_FRAMES import DATE_ASC import DATE_DESC -import DEVICE_TYPE_LS1 -import DEVICE_TYPE_NANO -import DEVICE_TYPE_NIR import FIRST_CONNECT_ERROR_ON_LOAD import LED_FRAMES import android.Manifest import android.app.Activity import android.bluetooth.BluetoothAdapter import android.content.Context -import android.content.DialogInterface import android.content.Intent import android.location.LocationManager import android.net.Uri import android.os.* import android.provider.Settings import android.util.Log -import android.widget.ListAdapter -import android.widget.ListView -import android.view.View import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog @@ -35,6 +28,11 @@ import androidx.navigation.Navigation import androidx.preference.PreferenceManager import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.* +import org.phenoapps.interfaces.spectrometers.Spectrometer +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_INDIGO +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_LS1 +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_NANO +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_NIR import org.phenoapps.prospector.BuildConfig import org.phenoapps.prospector.NavigationRootDirections import org.phenoapps.prospector.R @@ -45,8 +43,9 @@ import org.phenoapps.prospector.data.viewmodels.MainActivityViewModel import org.phenoapps.prospector.data.viewmodels.devices.InnoSpectraViewModel import org.phenoapps.prospector.data.viewmodels.devices.LinkSquareViewModel import org.phenoapps.prospector.databinding.ActivityMainBinding -import org.phenoapps.prospector.interfaces.Spectrometer import org.phenoapps.prospector.utils.* +import org.phenoapps.security.Security +import org.phenoapps.viewmodels.spectrometers.Indigo import org.phenoapps.utils.IntentUtil import java.io.File @@ -74,6 +73,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { private val sViewModel: MainActivityViewModel by viewModels() + val advisor by Security().secureBluetoothActivity() + /** * This activity view model is used throughout all the fragments to update connection status. */ @@ -135,13 +136,22 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { } val maker = mPrefs.getString(mKeyUtil.deviceMaker, DEVICE_TYPE_LS1) - sDeviceViewModel = if (maker == DEVICE_TYPE_LS1) { + sDeviceViewModel = when (maker) { + in setOf(DEVICE_TYPE_LS1, DEVICE_TYPE_NIR) -> { - LinkSquareViewModel() + LinkSquareViewModel() - } else { + } + DEVICE_TYPE_NANO -> { - InnoSpectraViewModel() + InnoSpectraViewModel() + + } + else -> { + + Indigo() + + } } mBinding = DataBindingUtil.setContentView(this@MainActivity, R.layout.activity_main) @@ -505,25 +515,50 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { fun switchInnoSpectra() { - stopDeviceConnection() + val current = mPrefs.getString(mKeyUtil.deviceMaker, null) - mPrefs.edit().putString(mKeyUtil.deviceMaker, DEVICE_TYPE_NANO).apply() + if (current != DEVICE_TYPE_NANO) { - sDeviceViewModel = InnoSpectraViewModel() + stopDeviceConnection() - runtimeBluetoothCheck() + mPrefs.edit().putString(mKeyUtil.deviceMaker, DEVICE_TYPE_NANO).apply() + + sDeviceViewModel = InnoSpectraViewModel() + runtimeBluetoothCheck() + } } fun switchLinkSquare() { - stopDeviceConnection() + val current = mPrefs.getString(mKeyUtil.deviceMaker, null) - mPrefs.edit().putString(mKeyUtil.deviceMaker, DEVICE_TYPE_LS1).apply() + if (current !in setOf(DEVICE_TYPE_LS1, DEVICE_TYPE_NIR)) { - sDeviceViewModel = LinkSquareViewModel() + stopDeviceConnection() - startDeviceConnection() + mPrefs.edit().putString(mKeyUtil.deviceMaker, DEVICE_TYPE_LS1).apply() + + sDeviceViewModel = LinkSquareViewModel() + + startDeviceConnection() + } + } + + fun switchIndigo() { + + val current = mPrefs.getString(mKeyUtil.deviceMaker, null) + + if (current != DEVICE_TYPE_INDIGO) { + + stopDeviceConnection() + + mPrefs.edit().putString(mKeyUtil.deviceMaker, DEVICE_TYPE_INDIGO).apply() + + sDeviceViewModel = Indigo() + + startDeviceConnection() + } } private fun runtimeBluetoothCheck() { @@ -736,23 +771,55 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { } fun startDeviceConnection() { - launch { - withContext(Dispatchers.IO) { - sDeviceViewModel?.connect(this@MainActivity.applicationContext) + + val maker = mPrefs.getString(mKeyUtil.deviceMaker, DEVICE_TYPE_LS1) + sDeviceViewModel = when (maker) { + in setOf(DEVICE_TYPE_LS1, DEVICE_TYPE_NIR) -> { + + LinkSquareViewModel() + + } + DEVICE_TYPE_NANO -> { + + InnoSpectraViewModel() + + } + else -> { + + Indigo() + } } - } - private fun stopDeviceConnection() { - launch { - withContext(Dispatchers.IO) { - if (sDeviceViewModel?.isConnected() == true) - sDeviceViewModel?.disconnect(this@MainActivity) + if (sDeviceViewModel?.isConnected() != true) { + + if (sDeviceViewModel is Indigo) { + + (sDeviceViewModel as? Indigo)?.let { indigo -> + advisor.withNearby { adapter -> + launch { + withContext(Dispatchers.IO) { + indigo.connect(adapter, this@MainActivity) + } + } + } + } + } else { + launch { + withContext(Dispatchers.IO) { + sDeviceViewModel?.connect(this@MainActivity.applicationContext) + } + } } } } + private fun stopDeviceConnection() { + if (sDeviceViewModel?.isConnected() == true) + sDeviceViewModel?.forceDisconnect() + } + override fun onDestroy() { stopDeviceConnection() @@ -769,15 +836,27 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { mConnectionHandlerThread.quit() + cancel() + super.onDestroy() } override fun onPause() { - launch { - withContext(Dispatchers.IO) { - sDeviceViewModel?.reset(this@MainActivity) + if (sDeviceViewModel is Indigo) { + (sDeviceViewModel as? Indigo)?.let { indigo -> + advisor.withNearby { adapter -> + + indigo.reset(adapter, this) + + } + } + } else { + launch { + withContext(Dispatchers.IO) { + sDeviceViewModel?.reset(this@MainActivity) + } } } @@ -884,4 +963,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { private fun updateLastOpenedTime() { mPrefs.edit().putLong(mKeyUtil.lastTimeAppOpened, System.nanoTime()).apply() } + + init { + advisor.initialize() + } } diff --git a/app/src/main/java/org/phenoapps/prospector/data/models/Scan.kt b/app/src/main/java/org/phenoapps/prospector/data/models/Scan.kt index 0b7d57e..4046f21 100644 --- a/app/src/main/java/org/phenoapps/prospector/data/models/Scan.kt +++ b/app/src/main/java/org/phenoapps/prospector/data/models/Scan.kt @@ -1,8 +1,8 @@ package org.phenoapps.prospector.data.models -import DEVICE_TYPE_NIR import androidx.annotation.Keep import androidx.room.* +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_NIR import org.phenoapps.prospector.utils.DateUtil @Keep diff --git a/app/src/main/java/org/phenoapps/prospector/data/viewmodels/devices/InnoSpectraViewModel.kt b/app/src/main/java/org/phenoapps/prospector/data/viewmodels/devices/InnoSpectraViewModel.kt index f20463b..fdc1f9f 100644 --- a/app/src/main/java/org/phenoapps/prospector/data/viewmodels/devices/InnoSpectraViewModel.kt +++ b/app/src/main/java/org/phenoapps/prospector/data/viewmodels/devices/InnoSpectraViewModel.kt @@ -1,6 +1,5 @@ package org.phenoapps.prospector.data.viewmodels.devices -import DEVICE_TYPE_NANO import android.Manifest import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager @@ -22,17 +21,20 @@ import androidx.lifecycle.viewModelScope import androidx.preference.PreferenceManager import com.ISCSDK.ISCNIRScanSDK import com.ISCSDK.ISCNIRScanSDK.* -import com.stratiotechnology.linksquareapi.LSFrame import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.phenoapps.interfaces.iot.Device +import org.phenoapps.interfaces.spectrometers.Spectrometer +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_NANO import org.phenoapps.prospector.R import org.phenoapps.prospector.fragments.preferences.InnoSpectraSettingsFragment import org.phenoapps.prospector.fragments.nano_configuration_creator.models.Config import org.phenoapps.prospector.interfaces.NanoEventListener -import org.phenoapps.prospector.interfaces.Spectrometer import org.phenoapps.prospector.receivers.DeviceInfoReceiver import org.phenoapps.prospector.utils.KeyUtil +import org.phenoapps.viewmodels.spectrometers.Frame import java.lang.IllegalArgumentException import javax.inject.Inject import kotlin.collections.ArrayList @@ -64,7 +66,7 @@ class InnoSpectraViewModel @Inject constructor() : ViewModel(), Spectrometer, Na private var mNanoReceiver: InnoSpectraBase? = null //live data that receivers will produce and listeners will consume - private var mSpectralData: Spectrometer.Frame? = null + private var mSpectralData: Frame? = null private var OnDeviceButtonClicked: (() -> Unit)? = null @@ -72,7 +74,7 @@ class InnoSpectraViewModel @Inject constructor() : ViewModel(), Spectrometer, Na private var mUiScan = false - private var mDeviceInfo: Spectrometer.DeviceInfo? = null + private var mDeviceInfo: Device.DeviceInfo? = null private var mDeviceStatus: InnoSpectraSettingsFragment.DeviceStatus? = null @@ -164,6 +166,7 @@ class InnoSpectraViewModel @Inject constructor() : ViewModel(), Spectrometer, Na override fun onServiceDisconnected(name: ComponentName?) { mConnected = false + mConnectionStarting = false mNanoSdk?.sdk?.disconnect() mNanoSdk?.sdk?.close() } @@ -238,6 +241,10 @@ class InnoSpectraViewModel @Inject constructor() : ViewModel(), Spectrometer, Na private fun isRefDataReady() = mRefDataReady + override fun forceDisconnect() { + onCleared() + } + override fun reset(context: Context?) { context?.let { ctx -> @@ -246,6 +253,8 @@ class InnoSpectraViewModel @Inject constructor() : ViewModel(), Spectrometer, Na viewModelScope.launch { + delay(3000L) + connect(ctx) } @@ -254,7 +263,7 @@ class InnoSpectraViewModel @Inject constructor() : ViewModel(), Spectrometer, Na override fun getDeviceError(): String { return "None" } - override fun getDeviceInfo() = mDeviceInfo ?: Spectrometer.DeviceInfo("?", "?", "?", "?", "?", DEVICE_TYPE_NANO) + override fun getDeviceInfo() = mDeviceInfo ?: Device.DeviceInfo("?", "?", "?", "?", "?", DEVICE_TYPE_NANO) override fun setEventListener(onClick: () -> Unit): LiveData = liveData { @@ -264,7 +273,7 @@ class InnoSpectraViewModel @Inject constructor() : ViewModel(), Spectrometer, Na } - override fun scan(context: Context, manual: Boolean?): LiveData?> = liveData { + override fun scan(context: Context, manual: Boolean?): LiveData?> = liveData { if (isConnected() && isRefDataReady()) { @@ -295,12 +304,12 @@ class InnoSpectraViewModel @Inject constructor() : ViewModel(), Spectrometer, Na mUiScan = false emit(listOf(frame).map { - LSFrame().apply { + Frame().apply { this.lightSource = it.lightSource this.length = it.length - this.frameNo = it.frameNo + this.frameIndex = it.frameIndex this.deviceType = it.deviceType - this.raw_data = it.raw_data + this.rawData = it.rawData this.data = it.data } }) @@ -351,7 +360,7 @@ class InnoSpectraViewModel @Inject constructor() : ViewModel(), Spectrometer, Na } } - override fun onScanDataReady(spectral: Spectrometer.Frame) { + override fun onScanDataReady(spectral: Frame) { if (isGattStateConnected()) { @@ -362,17 +371,6 @@ class InnoSpectraViewModel @Inject constructor() : ViewModel(), Spectrometer, Na override fun onGetUuid(uuid: String) { - mDeviceInfo?.let { info -> -// mDeviceInfo = Spectrometer.DeviceInfo( -// info.softwareVersion, -// info.hardwareVersion, -// uuid, -// info.alias, -// info.opMode, -// info.deviceType -// ) - } - GetDeviceStatus() } @@ -380,7 +378,7 @@ class InnoSpectraViewModel @Inject constructor() : ViewModel(), Spectrometer, Na override fun onGetDeviceInfo(info: DeviceInfoReceiver.NanoDeviceInfo) { mDeviceInfo = with(info) { - Spectrometer.DeviceInfo( + Device.DeviceInfo( this.spec, this.hardware, this.model, @@ -592,4 +590,14 @@ class InnoSpectraViewModel @Inject constructor() : ViewModel(), Spectrometer, Na """.trimIndent() } + override fun onCleared() { + super.onCleared() + try { + //viewModelScope.cancel() + mNanoSdk?.sdk?.disconnect() + mNanoSdk?.sdk?.close() + } catch (e: Exception) { + e.printStackTrace() + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/phenoapps/prospector/data/viewmodels/devices/LinkSquareViewModel.kt b/app/src/main/java/org/phenoapps/prospector/data/viewmodels/devices/LinkSquareViewModel.kt index 6a73f6a..cf7915e 100644 --- a/app/src/main/java/org/phenoapps/prospector/data/viewmodels/devices/LinkSquareViewModel.kt +++ b/app/src/main/java/org/phenoapps/prospector/data/viewmodels/devices/LinkSquareViewModel.kt @@ -3,8 +3,6 @@ package org.phenoapps.prospector.data.viewmodels.devices import BULB_FRAMES import DEVICE_IP import DEVICE_PORT -import DEVICE_TYPE_LS1 -import DEVICE_TYPE_NIR import LED_FRAMES import android.content.Context import android.util.Log @@ -15,7 +13,11 @@ import com.stratiotechnology.linksquareapi.LSFrame import com.stratiotechnology.linksquareapi.LinkSquareAPI import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.* -import org.phenoapps.prospector.interfaces.Spectrometer +import org.phenoapps.interfaces.iot.Device +import org.phenoapps.interfaces.spectrometers.Spectrometer +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_LS1 +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_NIR +import org.phenoapps.viewmodels.spectrometers.Frame import java.io.BufferedReader import java.io.FileReader import java.lang.IndexOutOfBoundsException @@ -61,6 +63,10 @@ class LinkSquareViewModel @Inject constructor() : ViewModel(), Spectrometer { } } + override fun forceDisconnect() { + onCleared() + } + override fun reset(context: Context?) { sDevice?.Close() @@ -74,7 +80,7 @@ class LinkSquareViewModel @Inject constructor() : ViewModel(), Spectrometer { override fun getDeviceError() = sDevice?.GetLSError() override fun getDeviceInfo() = with(sDevice?.GetDeviceInfo()) { - Spectrometer.DeviceInfo( + Device.DeviceInfo( this?.SWVersion ?: "?", this?.HWVersion ?: "?", this?.DeviceID ?: "-1", @@ -116,7 +122,7 @@ class LinkSquareViewModel @Inject constructor() : ViewModel(), Spectrometer { emit("DONE") } - override fun scan(context: Context, manual: Boolean?): LiveData?> = liveData { + override fun scan(context: Context, manual: Boolean?): LiveData?> = liveData { with (manager(context)) { @@ -126,7 +132,14 @@ class LinkSquareViewModel @Inject constructor() : ViewModel(), Spectrometer { val frames = scan(ledFrames, bulbFrames) - emit(frames) + emit(frames.map { + Frame(length = it.length, + deviceType = if (it.deviceType == 0) DEVICE_TYPE_LS1 else DEVICE_TYPE_NIR, + data = it.data, + lightSource = it.lightSource.toInt(), + frameIndex = it.frameNo, + rawData = it.raw_data.joinToString(" ") { f -> "$f" }) + }) } } diff --git a/app/src/main/java/org/phenoapps/prospector/fragments/ConnectionFragment.kt b/app/src/main/java/org/phenoapps/prospector/fragments/ConnectionFragment.kt index a95fd8c..8b59556 100644 --- a/app/src/main/java/org/phenoapps/prospector/fragments/ConnectionFragment.kt +++ b/app/src/main/java/org/phenoapps/prospector/fragments/ConnectionFragment.kt @@ -4,8 +4,11 @@ import android.os.Handler import android.os.HandlerThread import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController import org.phenoapps.prospector.R import org.phenoapps.prospector.activities.MainActivity +import org.phenoapps.security.Security +import org.phenoapps.viewmodels.spectrometers.Indigo open class ConnectionFragment(id: Int) : Fragment(id) { @@ -27,7 +30,6 @@ open class ConnectionFragment(id: Int) : Fragment(id) { } else R.drawable.ic_vector_difference_ab ) - } } } diff --git a/app/src/main/java/org/phenoapps/prospector/fragments/ExperimentListFragment.kt b/app/src/main/java/org/phenoapps/prospector/fragments/ExperimentListFragment.kt index a314c95..18a7016 100644 --- a/app/src/main/java/org/phenoapps/prospector/fragments/ExperimentListFragment.kt +++ b/app/src/main/java/org/phenoapps/prospector/fragments/ExperimentListFragment.kt @@ -35,6 +35,7 @@ import org.phenoapps.prospector.data.viewmodels.ExperimentViewModel import org.phenoapps.prospector.databinding.FragmentExperimentListBinding import org.phenoapps.prospector.utils.Dialogs import org.phenoapps.prospector.utils.KeyUtil +import org.phenoapps.viewmodels.spectrometers.Indigo /** * The main data fragment that displays the top-level experiment hierarchy. @@ -146,20 +147,29 @@ class ExperimentListFragment : ConnectionFragment(R.layout.fragment_experiment_l R.id.action_connection -> { - val deviceViewModel = (activity as MainActivity).sDeviceViewModel + with (activity as? MainActivity) { - if (deviceViewModel?.isConnected() == true) { + if (this?.mConnected == true) { - deviceViewModel.reset(context) + if (sDeviceViewModel is Indigo) { + (sDeviceViewModel as? Indigo)?.let { indigo -> + advisor.withNearby { adapter -> - } else { + indigo.reset(adapter, context) + + } + } + } else { + sDeviceViewModel?.reset(context) + } + + } else { - if (findNavController().currentDestination?.id == R.id.experiment_list_fragment) { findNavController().navigate(ExperimentListFragmentDirections .actionToConnectInstructions()) - } - (activity as? MainActivity)?.startDeviceConnection() + this?.startDeviceConnection() + } } } } diff --git a/app/src/main/java/org/phenoapps/prospector/fragments/SampleListFragment.kt b/app/src/main/java/org/phenoapps/prospector/fragments/SampleListFragment.kt index ae1fdf1..3973f1c 100644 --- a/app/src/main/java/org/phenoapps/prospector/fragments/SampleListFragment.kt +++ b/app/src/main/java/org/phenoapps/prospector/fragments/SampleListFragment.kt @@ -5,8 +5,6 @@ import ALPHA_DESC import CONVERT_TO_WAVELENGTHS import DATE_ASC import DATE_DESC -import DEVICE_TYPE_LS1 -import DEVICE_TYPE_NIR import android.net.Uri import android.os.Bundle import android.os.Handler @@ -28,15 +26,21 @@ import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.WithFragmentBindings import kotlinx.coroutines.* +import org.phenoapps.interfaces.spectrometers.Spectrometer +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_LS1 +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_NIR import org.phenoapps.prospector.R import org.phenoapps.prospector.activities.MainActivity import org.phenoapps.prospector.adapter.SampleAdapter import org.phenoapps.prospector.data.models.Sample import org.phenoapps.prospector.data.viewmodels.SampleViewModel +import org.phenoapps.prospector.data.viewmodels.devices.InnoSpectraViewModel +import org.phenoapps.prospector.data.viewmodels.devices.LinkSquareViewModel import org.phenoapps.prospector.databinding.FragmentSampleListBinding import org.phenoapps.prospector.interfaces.SampleListClickListener import org.phenoapps.prospector.utils.* import org.phenoapps.utils.BaseDocumentTreeUtil +import org.phenoapps.viewmodels.spectrometers.Indigo /** * Similar to the experiment fragment, this displays lists of samples for a given experiment. @@ -268,16 +272,20 @@ class SampleListFragment : ConnectionFragment(R.layout.fragment_sample_list), Co mDeviceType = deviceType mName = name - val maker = mPrefs.getString(mKeyUtil.deviceMaker, DEVICE_TYPE_LS1) ?: DEVICE_TYPE_LS1 - if (maker !in deviceType) { - - if (DEVICE_TYPE_LS1 in deviceType) { + when (mDeviceType) { + in setOf(DEVICE_TYPE_LS1, DEVICE_TYPE_NIR) -> { (activity as MainActivity).switchLinkSquare() - } else { + } + Spectrometer.DEVICE_TYPE_NANO -> { (activity as MainActivity).switchInnoSpectra() + + } + else -> { + + (activity as MainActivity).switchIndigo() } } @@ -354,20 +362,27 @@ class SampleListFragment : ConnectionFragment(R.layout.fragment_sample_list), Co R.id.action_connection -> { - val deviceViewModel = (activity as MainActivity).sDeviceViewModel - with (activity as? MainActivity) { if (this?.mConnected == true) { - deviceViewModel?.reset(context) - } else { + if (sDeviceViewModel is Indigo) { + (sDeviceViewModel as? Indigo)?.let { indigo -> + advisor.withNearby { adapter -> - if (findNavController().currentDestination?.id == R.id.sample_list_fragment) { - findNavController().navigate(SampleListFragmentDirections - .actionToConnectInstructions()) + indigo.reset(adapter, context) + + } + } + } else { + sDeviceViewModel?.reset(context) } + } else { + + findNavController().navigate(SampleListFragmentDirections + .actionToConnectInstructions()) + this?.startDeviceConnection() } } diff --git a/app/src/main/java/org/phenoapps/prospector/fragments/ScanListFragment.kt b/app/src/main/java/org/phenoapps/prospector/fragments/ScanListFragment.kt index a378716..319f6f7 100644 --- a/app/src/main/java/org/phenoapps/prospector/fragments/ScanListFragment.kt +++ b/app/src/main/java/org/phenoapps/prospector/fragments/ScanListFragment.kt @@ -2,9 +2,6 @@ package org.phenoapps.prospector.fragments import CONVERT_TO_WAVELENGTHS import DEVICE_ALIAS -import DEVICE_TYPE_LS1 -import DEVICE_TYPE_NANO -import DEVICE_TYPE_NIR import OPERATOR import android.graphics.Color import android.os.Bundle @@ -16,14 +13,12 @@ import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.databinding.DataBindingUtil -import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import com.google.android.material.tabs.TabLayout -import com.stratiotechnology.linksquareapi.LSFrame import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.WithFragmentBindings import kotlinx.coroutines.* @@ -39,9 +34,19 @@ import org.phenoapps.prospector.utils.* import java.lang.IllegalStateException import java.util.* import com.github.mikephil.charting.components.XAxis +import org.phenoapps.interfaces.iot.Device +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_INDIGO +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_LS1 +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_NANO +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_NIR import org.phenoapps.prospector.data.viewmodels.devices.InnoSpectraViewModel import org.phenoapps.prospector.data.viewmodels.devices.LinkSquareViewModel -import org.phenoapps.prospector.interfaces.Spectrometer +import org.phenoapps.security.Security +import org.phenoapps.viewmodels.spectrometers.Frame +import org.phenoapps.viewmodels.spectrometers.Indigo +import org.phenoapps.viewmodels.spectrometers.IndigoViewModel +import org.phenoapps.viewmodels.spectrometers.IndigoViewModel.Companion.SAMPLE_SERVICE +import org.phenoapps.viewmodels.spectrometers.IndigoViewModel.Companion.SAMPLE_TRANSMIT /** @@ -117,7 +122,7 @@ class ScanListFragment : ConnectionFragment(R.layout.fragment_scan_list), Corout } } - private suspend fun insertScan(name: String, frames: List) { + private suspend fun insertScan(name: String, frames: List) { val deviceViewModel = (activity as MainActivity).sDeviceViewModel @@ -146,26 +151,41 @@ class ScanListFragment : ConnectionFragment(R.layout.fragment_scan_list), Corout val sid = sViewModel.insertScanAsync(scan).await() - if (deviceViewModel is LinkSquareViewModel) { - frames.forEach { frame -> + when (deviceViewModel) { + is LinkSquareViewModel -> { + frames.forEach { frame -> - sViewModel.insertFrame(sid, SpectralFrame( - sid, - frame.frameNo, - frame.raw_data.joinToString(" ") { value -> value.toString() }, - frame.lightSource.toInt()) - ) + sViewModel.insertFrame(sid, SpectralFrame( + sid, + frame.frameIndex ?: 0, + frame.rawData ?: String(), + frame.lightSource ?: 0) + ) + } } - } else { - frames.forEach { frame -> - - sViewModel.insertFrame(sid, SpectralFrame( - sid, - frame.frameNo, - frame.raw_data.joinToString(" ") { value -> value.toString() }, - frame.lightSource.toInt(), - wavelengths = frame.data.joinToString(" ") { value -> value.toString() } - )) + is InnoSpectraViewModel -> { + frames.forEach { frame -> + + sViewModel.insertFrame(sid, SpectralFrame( + sid, + frame.frameIndex ?: 0, + frame.rawData ?: String(), + frame.lightSource ?: 0, + wavelengths = frame.data?.joinToString(" ") { value -> value.toString() } + )) + } + } + else -> { + frames.forEach { frame -> + + sViewModel.insertFrame(sid, SpectralFrame( + sid, + frame.frameIndex ?: 0, + frame.rawData ?: String(), + frame.lightSource ?: 0, + wavelengths = frame.wavelengths + )) + } } } } @@ -174,7 +194,7 @@ class ScanListFragment : ConnectionFragment(R.layout.fragment_scan_list), Corout * Dialog is only called if the current connected deviceType matches the experiment deviceType. * If they match, the dialog begins which displays the status of the scan (indeterminately) */ - private fun callScanDialog(device: Spectrometer.DeviceInfo, manual: Boolean? = false) { + private fun callScanDialog(device: Device.DeviceInfo, manual: Boolean? = false) { if (mScansEnabled) { @@ -188,7 +208,7 @@ class ScanListFragment : ConnectionFragment(R.layout.fragment_scan_list), Corout if (!deviceViewModel.hasActiveScan()) { - (activity as? MainActivity)?.notify(R.string.inno_spectra_device_not_ready) + (activity as? MainActivity)?.notify(R.string.device_not_ready) mIsScanning = false return @@ -212,7 +232,16 @@ class ScanListFragment : ConnectionFragment(R.layout.fragment_scan_list), Corout it?.let { frames -> - sDeviceScope.launch { + if (frames.first().length == -1) { //failed + + activity?.runOnUiThread { + dialogInterface.dismiss() + loadGraph() + mIsScanning = false + (activity as? MainActivity)?.notify(R.string.scan_failed) + } + + } else sDeviceScope.launch { withContext(Dispatchers.IO) { @@ -322,6 +351,15 @@ class ScanListFragment : ConnectionFragment(R.layout.fragment_scan_list), Corout ui.fragScanListLineChart.xAxis.position = XAxis.XAxisPosition.BOTTOM ui.scanOnClick = sOnClickScan + if (deviceViewModel is Indigo) { + deviceViewModel.isConnectedLive().observe(viewLifecycleOwner) { connected -> + ui.scanOnClick = if (!connected) { + View.OnClickListener { + (activity as? MainActivity)?.notify(R.string.device_not_ready) + } + } else sOnClickScan + } + } //check if experiment id is included in the arguments. mExpId = arguments?.getLong("experiment", -1L) ?: -1L @@ -427,49 +465,23 @@ class ScanListFragment : ConnectionFragment(R.layout.fragment_scan_list), Corout val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext()) - tabLayout.getTabAt(if (prefs.getBoolean(mKeyUtil.lastSelectedGraph, false)) { 1 } else 0) - ?.select() - + tabLayout.getTabAt( + when { + (activity as? MainActivity)?.sDeviceViewModel is Indigo -> 1 + prefs.getBoolean(mKeyUtil.lastSelectedGraph, false) -> 1 + else -> 0 + } + )?.select() } private fun FragmentScanListBinding.setupToolbar() { - val deviceViewModel = (activity as MainActivity).sDeviceViewModel - scanToolbar.setNavigationOnClickListener { findNavController().popBackStack() } - toolbar.setOnMenuItemClickListener { - - when(it.itemId) { - - R.id.action_connection -> { - - with (activity as? MainActivity) { - - if (this?.mConnected == true) { - - deviceViewModel?.reset(context) - - } else { - - if (findNavController().currentDestination?.id == R.id.scan_list_fragment) { - findNavController().navigate(ScanListFragmentDirections - .actionToConnectInstructions()) - } - - this?.startDeviceConnection() - } - } - } - } - - true - } - scanToolbar.setOnMenuItemClickListener { when(it.itemId) { @@ -561,6 +573,8 @@ class ScanListFragment : ConnectionFragment(R.layout.fragment_scan_list), Corout DEVICE_TYPE_LS1 -> LinkSquareRange.max + DEVICE_TYPE_INDIGO -> IndigoRange.max + else -> InnoSpectraRange.max } @@ -702,6 +716,42 @@ class ScanListFragment : ConnectionFragment(R.layout.fragment_scan_list), Corout } } } + + mBinding?.toolbar?.setOnMenuItemClickListener { item -> + + when(item.itemId) { + + R.id.action_connection -> { + + with (activity as? MainActivity) { + + if (this?.mConnected == true) { + + if (sDeviceViewModel is Indigo) { + (sDeviceViewModel as? Indigo)?.let { indigo -> + advisor.withNearby { adapter -> + + indigo.reset(adapter, context) + + } + } + } else { + sDeviceViewModel?.reset(context) + } + + } else { + + findNavController().navigate(ScanListFragmentDirections + .actionToConnectInstructions()) + + this?.startDeviceConnection() + } + } + } + } + + true + } } /** @@ -729,7 +779,7 @@ class ScanListFragment : ConnectionFragment(R.layout.fragment_scan_list), Corout } } - }?.observe(viewLifecycleOwner, {}) + }?.observe(viewLifecycleOwner) {} } catch (e: IllegalStateException) { @@ -781,6 +831,11 @@ class ScanListFragment : ConnectionFragment(R.layout.fragment_scan_list), Corout override fun onPause() { super.onPause() + //sDeviceScope.cancel() + } + + override fun onDestroy() { + super.onDestroy() sDeviceScope.cancel() } } \ No newline at end of file diff --git a/app/src/main/java/org/phenoapps/prospector/fragments/preferences/SettingsFragment.kt b/app/src/main/java/org/phenoapps/prospector/fragments/preferences/SettingsFragment.kt index d8105fc..25e48cb 100644 --- a/app/src/main/java/org/phenoapps/prospector/fragments/preferences/SettingsFragment.kt +++ b/app/src/main/java/org/phenoapps/prospector/fragments/preferences/SettingsFragment.kt @@ -1,5 +1,6 @@ package org.phenoapps.prospector.fragments.preferences +import DEVICE_INDIGO import DEVICE_INNO_SPECTRA import DEVICE_LINK_SQUARE import OPERATOR @@ -104,6 +105,17 @@ class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope by MainScope } } + findPreference(DEVICE_INDIGO)?.let { devicePref -> + + devicePref.setOnPreferenceClickListener { + + findNavController().navigate(SettingsContainerFragmentDirections + .actionToIndigoSettingsFragment()) + + true + } + } + findPreference(mKeyUtil.database)?.let { databasePref -> databasePref.setOnPreferenceClickListener { diff --git a/app/src/main/java/org/phenoapps/prospector/fragments/preferences/indigo/DeviceSearchFragment.kt b/app/src/main/java/org/phenoapps/prospector/fragments/preferences/indigo/DeviceSearchFragment.kt new file mode 100644 index 0000000..3567ed7 --- /dev/null +++ b/app/src/main/java/org/phenoapps/prospector/fragments/preferences/indigo/DeviceSearchFragment.kt @@ -0,0 +1,102 @@ +package org.phenoapps.prospector.fragments.preferences.indigo + +import android.annotation.SuppressLint +import android.bluetooth.le.ScanCallback +import android.bluetooth.le.ScanResult +import android.os.Build +import android.widget.Toast +import androidx.annotation.RequiresApi +import androidx.lifecycle.liveData +import androidx.navigation.fragment.findNavController +import kotlinx.coroutines.delay +import org.phenoapps.adapters.bluetooth.BluetoothDeviceAdapter +import org.phenoapps.fragments.bluetooth.BluetoothListFragment +import org.phenoapps.models.bluetooth.BluetoothDeviceModel +import org.phenoapps.prospector.R + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +class DeviceSearchFragment: BluetoothListFragment() { + + companion object { + const val LIVE_DATA_DELAY_MS = 2000L + } + + private var mDeviceSet = hashSetOf() + + private val mScanCallback = object : ScanCallback() { + override fun onScanResult(callbackType: Int, result: ScanResult?) { + super.onScanResult(callbackType, result) + + result?.device?.let { d -> + + if (d.address !in mDeviceSet.map { it.device.address }) { + + //Toast.makeText(context, "Found new device: ${d.address}.", Toast.LENGTH_SHORT).show() + + mDeviceSet.add(BluetoothDeviceModel(d)) + } + } + } + } + + override fun onRecyclerReady() { + + liveDeviceData().observe(viewLifecycleOwner) { devices -> + + (mRecyclerView.adapter as? BluetoothDeviceAdapter) + ?.submitList(devices.toList()) + + } + } + + private fun liveDeviceData() = liveData> { + + while (true) { + + delay(LIVE_DATA_DELAY_MS) + + emit(mDeviceSet) + + } + } + + @SuppressLint("MissingPermission") //handled with advisor + override fun onPause() { + super.onPause() + + advisor.withNearby { adapter -> + + adapter.cancelDiscovery() + + adapter.bluetoothLeScanner.stopScan(mScanCallback) + + } + } + + @SuppressLint("MissingPermission") //handled with advisor + override fun onResume() { + + advisor.withNearby { adapter -> + + adapter.startDiscovery() + + adapter.bluetoothLeScanner.startScan(mScanCallback) + } + + super.onResume() + } + + override fun onItemClicked(model: Any) { + + if (model is BluetoothDeviceModel) { + + val deviceSelected = context?.getString(R.string.pref_indigo_device_search_selected, model.device.address) ?: model.device.address + + Toast.makeText(context, deviceSelected, Toast.LENGTH_SHORT).show() + + mPrefs.edit().putString(mKeys.argBluetoothDeviceAddress, model.device.address).apply() + + findNavController().popBackStack() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/phenoapps/prospector/fragments/preferences/indigo/IndigoSettingsContainerFragment.kt b/app/src/main/java/org/phenoapps/prospector/fragments/preferences/indigo/IndigoSettingsContainerFragment.kt new file mode 100644 index 0000000..9ed2fbd --- /dev/null +++ b/app/src/main/java/org/phenoapps/prospector/fragments/preferences/indigo/IndigoSettingsContainerFragment.kt @@ -0,0 +1,11 @@ +package org.phenoapps.prospector.fragments.preferences.indigo + +import android.graphics.Color +import org.phenoapps.fragments.toolbar.PopOnBackToolbarFragment + +class IndigoSettingsContainerFragment: PopOnBackToolbarFragment() { + + override val containedFragment = IndigoSettingsFragment() + + override val topToolbarColor: Int = Color.parseColor("#03A9F4") +} \ No newline at end of file diff --git a/app/src/main/java/org/phenoapps/prospector/fragments/preferences/indigo/IndigoSettingsFragment.kt b/app/src/main/java/org/phenoapps/prospector/fragments/preferences/indigo/IndigoSettingsFragment.kt new file mode 100644 index 0000000..df5fa74 --- /dev/null +++ b/app/src/main/java/org/phenoapps/prospector/fragments/preferences/indigo/IndigoSettingsFragment.kt @@ -0,0 +1,51 @@ +package org.phenoapps.prospector.fragments.preferences.indigo + +import android.os.Bundle +import androidx.navigation.fragment.findNavController +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import org.phenoapps.prospector.R +import org.phenoapps.prospector.utils.KeyUtil + +class IndigoSettingsFragment: PreferenceFragmentCompat() { + + private val keys by lazy { + KeyUtil(context) + } + + private val libKeys by lazy { + org.phenoapps.utils.KeyUtil(context) + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.indigo_preferences, rootKey) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setupIndigoSearchPref() + } + + private fun setupIndigoSearchPref() { + findPreference(keys.indigoSearchDevice)?.let { pref -> + pref.setOnPreferenceClickListener { + + findNavController().navigate(IndigoSettingsContainerFragmentDirections + .actionIndigoSettingsToDeviceSearch()) + + true + } + + val summary = preferenceManager.sharedPreferences.getString(libKeys.argBluetoothDeviceAddress, null) + if (summary != null) { + pref.summary = summary + } + } + } + + override fun onResume() { + super.onResume() + setupIndigoSearchPref() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/phenoapps/prospector/fragments/tutorials/ConnectionInstructionsFragment.kt b/app/src/main/java/org/phenoapps/prospector/fragments/tutorials/ConnectionInstructionsFragment.kt index 1f8577a..f247565 100644 --- a/app/src/main/java/org/phenoapps/prospector/fragments/tutorials/ConnectionInstructionsFragment.kt +++ b/app/src/main/java/org/phenoapps/prospector/fragments/tutorials/ConnectionInstructionsFragment.kt @@ -36,13 +36,15 @@ class ConnectionInstructionsFragment : Fragment(), OnModelClickListener { val lsIcon = R.mipmap.linksquare_logo val isTitle = ctx.getString(R.string.innospectra_nano) val isIcon = R.mipmap.isc_logo + val indigoTitle = getString(R.string.indigo) + val indigoIcon = R.drawable.indigo mBinding?.fragDeviceConnectionTutorialIndexRv?.adapter = SpectrometerAdapter(this).also { it.submitList(mutableListOf( SpectrometerAdapter.SpectrometerListItem(0, isTitle, isIcon), - SpectrometerAdapter.SpectrometerListItem(1, lsTitle, lsIcon)) + SpectrometerAdapter.SpectrometerListItem(1, lsTitle, lsIcon), + SpectrometerAdapter.SpectrometerListItem(2, indigoTitle, indigoIcon)) ) } - } mBinding?.toolbar?.setNavigationOnClickListener { @@ -71,6 +73,9 @@ class ConnectionInstructionsFragment : Fragment(), OnModelClickListener { 1 -> findNavController().navigate(ConnectionInstructionsFragmentDirections .actionFromIndexToLinksquareTutorial()) + + 2 -> findNavController().navigate(ConnectionInstructionsFragmentDirections + .actionFromIndexToIndigoTutorial()) } } } diff --git a/app/src/main/java/org/phenoapps/prospector/fragments/tutorials/IndigoInstructionsFragment.kt b/app/src/main/java/org/phenoapps/prospector/fragments/tutorials/IndigoInstructionsFragment.kt new file mode 100644 index 0000000..5f5dfc9 --- /dev/null +++ b/app/src/main/java/org/phenoapps/prospector/fragments/tutorials/IndigoInstructionsFragment.kt @@ -0,0 +1,45 @@ +package org.phenoapps.prospector.fragments.tutorials + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.view.ContextThemeWrapper +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import org.phenoapps.prospector.R +import org.phenoapps.prospector.activities.MainActivity +import org.phenoapps.prospector.databinding.FragmentIndigoConnectInstructionsBinding + +/** + * A simple fragment that displays a scroll view of instructions. + * Includes connection instructions and basic app usage. + */ +class IndigoInstructionsFragment : Fragment() { + + private var mBinding: FragmentIndigoConnectInstructionsBinding? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + + val contextThemeWrapper = ContextThemeWrapper(activity, R.style.AppTheme) + + val localInflater = inflater.cloneInContext(contextThemeWrapper) + + mBinding = DataBindingUtil.inflate(localInflater, R.layout.fragment_indigo_connect_instructions, null, false) + + mBinding?.toolbar?.setNavigationOnClickListener { + findNavController().popBackStack() + } + + return mBinding?.root + + } + + override fun onResume() { + super.onResume() + + (activity as? MainActivity)?.setToolbar(R.id.action_nav_data) + + } +} \ No newline at end of file diff --git a/app/src/main/java/org/phenoapps/prospector/interfaces/NanoEventListener.kt b/app/src/main/java/org/phenoapps/prospector/interfaces/NanoEventListener.kt index cf3d50b..2089f39 100644 --- a/app/src/main/java/org/phenoapps/prospector/interfaces/NanoEventListener.kt +++ b/app/src/main/java/org/phenoapps/prospector/interfaces/NanoEventListener.kt @@ -3,6 +3,7 @@ package org.phenoapps.prospector.interfaces import com.ISCSDK.ISCNIRScanSDK import org.phenoapps.prospector.fragments.preferences.InnoSpectraSettingsFragment import org.phenoapps.prospector.receivers.DeviceInfoReceiver +import org.phenoapps.viewmodels.spectrometers.Frame interface NanoEventListener { @@ -10,7 +11,7 @@ interface NanoEventListener { fun onRefDataReady() = Unit - fun onScanDataReady(spectral: Spectrometer.Frame) = Unit + fun onScanDataReady(spectral: Frame) = Unit fun onScanStarted() = Unit diff --git a/app/src/main/java/org/phenoapps/prospector/interfaces/Spectrometer.kt b/app/src/main/java/org/phenoapps/prospector/interfaces/Spectrometer.kt deleted file mode 100644 index fb600ea..0000000 --- a/app/src/main/java/org/phenoapps/prospector/interfaces/Spectrometer.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.phenoapps.prospector.interfaces - -import android.content.Context -import android.content.SharedPreferences -import androidx.lifecycle.LiveData -import androidx.preference.PreferenceManager -import com.stratiotechnology.linksquareapi.LSFrame - -interface Spectrometer { - - data class Frame(var length: Int, - var lightSource: Byte = 0, - var frameNo: Int, - var deviceType: Int, - var data: FloatArray, - var raw_data: FloatArray) - - data class DeviceInfo(val softwareVersion: String, - val hardwareVersion: String, - val deviceId: String, - val alias: String, - val opMode: String, - val deviceType: String, - val serialNumber: String? = null, - val humidity: String? = null, - val temperature: String? = null) - - fun manager(context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - - suspend fun connect(context: Context) - - fun disconnect(context: Context): Int? - - fun isConnected(): Boolean - - fun reset(context: Context?) = Unit - - fun getDeviceError(): String? - - fun getDeviceInfo(): DeviceInfo - - fun setEventListener(onClick: () -> Unit): LiveData - - fun scan(context: Context, manual: Boolean? = false): LiveData?> -} \ No newline at end of file diff --git a/app/src/main/java/org/phenoapps/prospector/receivers/ScanDataReadyReceiver.kt b/app/src/main/java/org/phenoapps/prospector/receivers/ScanDataReadyReceiver.kt index a59c6eb..5491f4e 100644 --- a/app/src/main/java/org/phenoapps/prospector/receivers/ScanDataReadyReceiver.kt +++ b/app/src/main/java/org/phenoapps/prospector/receivers/ScanDataReadyReceiver.kt @@ -5,8 +5,10 @@ import android.content.Context import android.content.Intent import android.util.Log import com.ISCSDK.ISCNIRScanSDK +import com.stratiotechnology.linksquareapi.LSFrame +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_NANO import org.phenoapps.prospector.interfaces.NanoEventListener -import org.phenoapps.prospector.interfaces.Spectrometer +import org.phenoapps.viewmodels.spectrometers.Frame class ScanDataReadyReceiver(private val listener: NanoEventListener) : BroadcastReceiver() { @@ -39,12 +41,14 @@ class ScanDataReadyReceiver(private val listener: NanoEventListener) : Broadcast intensityData[i] = intensity.toFloat() } - listener.onScanDataReady(Spectrometer.Frame( + //TODO replace device type with string + listener.onScanDataReady(Frame( length = size, lightSource = 0, - frameNo = 1, - deviceType = 3, + frameIndex = 1, + deviceType = DEVICE_TYPE_NANO, data = reflectanceData, - raw_data = intensityData)) + rawData = intensityData.joinToString(" ") { "$it" }) + ) } } \ No newline at end of file diff --git a/app/src/main/java/org/phenoapps/prospector/utils/FileUtil.kt b/app/src/main/java/org/phenoapps/prospector/utils/FileUtil.kt index a9e36f3..207ee4c 100644 --- a/app/src/main/java/org/phenoapps/prospector/utils/FileUtil.kt +++ b/app/src/main/java/org/phenoapps/prospector/utils/FileUtil.kt @@ -1,15 +1,17 @@ package org.phenoapps.prospector.utils -import DEVICE_TYPE_LS1 -import DEVICE_TYPE_NIR import android.content.Context import android.net.Uri import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.apache.commons.csv.CSVFormat import org.apache.commons.csv.CSVPrinter +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_INDIGO +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_LS1 +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_NIR import org.phenoapps.prospector.R import org.phenoapps.prospector.data.models.DeviceTypeExport +import kotlin.math.min /** * handles file io for exporting and importing data; although this application currently doesn't import data. @@ -89,6 +91,8 @@ open class FileUtil(private val ctx: Context) { DEVICE_TYPE_LS1 -> LinkSquareExportRange.max + DEVICE_TYPE_INDIGO -> IndigoExportRange.max + else -> InnoSpectraExportRange.max } @@ -98,6 +102,8 @@ open class FileUtil(private val ctx: Context) { DEVICE_TYPE_LS1 -> LinkSquareExportRange.min + DEVICE_TYPE_INDIGO -> IndigoExportRange.min + else -> InnoSpectraExportRange.min } @@ -119,7 +125,7 @@ open class FileUtil(private val ctx: Context) { export.sample, export.date, export.deviceType, - export.deviceId, + export.deviceId.replace("[\" ]", ""), export.serial, export.humidity, export.temperature, @@ -136,6 +142,8 @@ open class FileUtil(private val ctx: Context) { DEVICE_TYPE_LS1 -> LinkSquareExportRange.max + DEVICE_TYPE_INDIGO -> IndigoExportRange.max + else -> InnoSpectraExportRange.max } @@ -145,6 +153,8 @@ open class FileUtil(private val ctx: Context) { DEVICE_TYPE_LS1 -> LinkSquareExportRange.min + DEVICE_TYPE_INDIGO -> IndigoExportRange.min + else -> InnoSpectraExportRange.min } @@ -180,7 +190,7 @@ open class FileUtil(private val ctx: Context) { e.sample, e.date, e.deviceType, - e.deviceId, + export.deviceId.replace("[\" ]", ""), e.serial, e.humidity, e.temperature, diff --git a/app/src/main/java/org/phenoapps/prospector/utils/KeyUtil.kt b/app/src/main/java/org/phenoapps/prospector/utils/KeyUtil.kt index 49932e9..7a54b27 100644 --- a/app/src/main/java/org/phenoapps/prospector/utils/KeyUtil.kt +++ b/app/src/main/java/org/phenoapps/prospector/utils/KeyUtil.kt @@ -58,4 +58,7 @@ class KeyUtil(private val ctx: Context?) { //changelog version val version by key(R.string.key_version) + //indigo + val indigoSearchDevice by key(R.string.key_pref_indigo_address) + } \ No newline at end of file diff --git a/app/src/main/java/org/phenoapps/prospector/utils/LinkSquareUtils.kt b/app/src/main/java/org/phenoapps/prospector/utils/LinkSquareUtils.kt index 602634c..1dc71ef 100644 --- a/app/src/main/java/org/phenoapps/prospector/utils/LinkSquareUtils.kt +++ b/app/src/main/java/org/phenoapps/prospector/utils/LinkSquareUtils.kt @@ -1,23 +1,22 @@ package org.phenoapps.prospector.utils -import DEVICE_TYPE_LS1 -import DEVICE_TYPE_NIR import android.content.Context import android.graphics.Color import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.Observer -import com.ISCSDK.ISCNIRScanSDK import com.github.mikephil.charting.charts.LineChart import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.LineData import com.github.mikephil.charting.data.LineDataSet import com.stratiotechnology.linksquareapi.LinkSquareAPI import org.apache.commons.math.stat.StatUtils +import org.phenoapps.interfaces.iot.Device +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_LS1 +import org.phenoapps.interfaces.spectrometers.Spectrometer.Companion.DEVICE_TYPE_NIR import org.phenoapps.prospector.R import org.phenoapps.prospector.data.models.DeviceTypeExport import org.phenoapps.prospector.fragments.ScanListFragment -import org.phenoapps.prospector.interfaces.Spectrometer class LinkSquareLightSources { companion object { @@ -71,6 +70,20 @@ class InnoSpectraExportRange { } } +class IndigoRange { + companion object { + const val min: Double = 700.0 + const val max: Double = 1100.0 + } +} + +class IndigoExportRange { + companion object { + const val min: Double = 700.0 + const val max: Double = 1100.0 + } +} + /** * Public helper functions for LinkSquare related processing. * @@ -335,7 +348,7 @@ fun buildLinkSquareDeviceInfo(context: Context, data: LinkSquareAPI.LSDeviceInfo * Used to translate the device id to a device type. * TODO: with the new LinkSquare 1.15 api, there is a DeviceType, must confirm that NIR=1 and LS=0 */ -fun resolveDeviceType(context: Context, data: Spectrometer.DeviceInfo): String = +fun resolveDeviceType(context: Context, data: Device.DeviceInfo): String = if (data.deviceId.startsWith("NIR")) { diff --git a/app/src/main/res/drawable-nodpi/indigo.png b/app/src/main/res/drawable-nodpi/indigo.png new file mode 100644 index 0000000..87a63e8 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/indigo.png differ diff --git a/app/src/main/res/drawable-nodpi/indigo_tutorial_1.png b/app/src/main/res/drawable-nodpi/indigo_tutorial_1.png new file mode 100644 index 0000000..4017282 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/indigo_tutorial_1.png differ diff --git a/app/src/main/res/drawable-nodpi/indigo_tutorial_2.png b/app/src/main/res/drawable-nodpi/indigo_tutorial_2.png new file mode 100644 index 0000000..2d90857 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/indigo_tutorial_2.png differ diff --git a/app/src/main/res/drawable-nodpi/indigo_tutorial_3.png b/app/src/main/res/drawable-nodpi/indigo_tutorial_3.png new file mode 100644 index 0000000..bed5df4 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/indigo_tutorial_3.png differ diff --git a/app/src/main/res/layout/fragment_indigo_connect_instructions.xml b/app/src/main/res/layout/fragment_indigo_connect_instructions.xml new file mode 100644 index 0000000..4e6c511 --- /dev/null +++ b/app/src/main/res/layout/fragment_indigo_connect_instructions.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/navigation.xml b/app/src/main/res/navigation/navigation.xml index de14099..a8c2cab 100644 --- a/app/src/main/res/navigation/navigation.xml +++ b/app/src/main/res/navigation/navigation.xml @@ -21,6 +21,10 @@ app:destination="@id/inno_spectra_settings_fragment" app:popUpTo="@id/settings_fragment" app:launchSingleTop="true"/> + @@ -82,6 +86,22 @@ + + + + + + + + + + + + @string/linksquare_nir @string/linksquare @string/innospectra_nano + @string/indigo 2.34 @@ -90,4 +91,5 @@ LinkSquareNIR InnoSpectra Nano InnoSpectra + Indigo \ No newline at end of file diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index 87f2e22..9cfefac 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -7,6 +7,7 @@ &package;VERSION + &package;indigo.DEVICE_ADDRESS &package;LAST_SELECTED_GRAPH &package;RETURN_FROM_NEW_CONFIG &package;LAST_CONNECTED_DEVICE_ID diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c5b2e80..787c08f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -96,6 +96,11 @@ Wait until the configurations are read, indicated by the progress bar. (Advanced) Create new scan configurations in the settings. It is highly recommended to use the default scan configurations. + Ensure your device is on, the top-most middle LED should be green, or red if already connected. Navigate to the settings in Prospector, choose Indigo, then Find a device. + Wait for BLE devices to appear, click on your Indigo device. + In the data tab, create a new experiment and select Indigo. This will initiate a device connection, please wait a moment. + If you can\'t find your device or scans are not completing, try to reset the device. + Step One Step Two Step Three @@ -199,6 +204,7 @@ Device Settings LinkSquare InnoSpectra + Indigo LinkSquare Settings Fragment InnoSpectra Fragment InnoSpectra @@ -245,7 +251,7 @@ Scans can only be repeated 1 to 65535 times. Restore Default Configs Resetting device to load default configurations. Please wait. - Please wait, device initializing. + Please wait, device initializing. Loading configs… You cannot choose this configuration, please manage saved configurations in the settings. Add new section to config @@ -326,6 +332,11 @@ Choose your device for instructions. Connection Guide Spectrometer Company + Indigo Settings + Click to search for an Indigo device to pair with. + Find a device + Selected device %s. + Scan failed, refreshing device connection. Nano Battery Low - "Scans may not be successful. " + Scans may not be successful. diff --git a/app/src/main/res/xml/indigo_preferences.xml b/app/src/main/res/xml/indigo_preferences.xml new file mode 100644 index 0000000..573254a --- /dev/null +++ b/app/src/main/res/xml/indigo_preferences.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 9102884..6cd100a 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -38,6 +38,11 @@ android:key="org.phenoapps.prospector.DEVICE_TYPE_INNO_SPECTRA" android:title="@string/pref_device_type_inno_spectra" /> + +