diff --git a/.idea/compiler.xml b/.idea/compiler.xml index fb7f4a8a465d42b4a0390d464b83b99e8465bba7..b589d56e9f285d8cfdc6c270853a5d439021a278 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="CompilerConfiguration"> - <bytecodeTargetLevel target="11" /> + <bytecodeTargetLevel target="17" /> </component> </project> \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index a2d7c21338e98a66cd8af9e352f293e52324608b..0897082f7512e48e89310db81b5455d997417505 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,15 +4,15 @@ <component name="GradleSettings"> <option name="linkedExternalProjectsSettings"> <GradleProjectSettings> - <option name="testRunner" value="GRADLE" /> - <option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="externalProjectPath" value="$PROJECT_DIR$" /> + <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" /> <option name="modules"> <set> <option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$/app" /> </set> </option> + <option name="resolveExternalAnnotations" value="false" /> </GradleProjectSettings> </option> </component> diff --git a/.idea/misc.xml b/.idea/misc.xml index 95d52db08d5d6c59fe131669c8f1bc1bad9e41a9..96be39756a27eae6deff7c29f6c8ce25ea9d7cee 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="DesignSurface"> <option name="filePathToZoomLevelMap"> @@ -25,7 +24,7 @@ </map> </option> </component> - <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK"> + <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/build/classes" /> </component> <component name="ProjectType"> diff --git a/app/build.gradle b/app/build.gradle index ad9df2f06a1dc6a1e10a1a025a226e09b3ba3f32..959f4062a285554b1bf3769c6b74398b68fc6765 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,12 +4,14 @@ plugins { } android { - compileSdk 32 + namespace = "com.example.mindrover" + + compileSdk 34 defaultConfig { applicationId "com.example.mindrover" - minSdk 28 - targetSdk 32 + minSdk 33 + targetSdk 35 versionCode 1 versionName "1.0" @@ -58,6 +60,8 @@ dependencies { // ViewModel implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.fragment:fragment-ktx:1.1.0' //Navigation implementation "androidx.navigation:navigation-fragment-ktx:2.5.1" @@ -72,6 +76,10 @@ dependencies { exclude group:"org.apache.commons", module:"commons-lang3" } + implementation(files("libs/mindRove-debug_v1_1.aar")) + implementation(fileTree("dir": "libs", "include": ["*.jar", "*.aar"])) + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0") + implementation("androidx.compose.runtime:runtime:1.6.1") // implementation 'org.tensorflow:tensorflow-lite:2.0.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0c5295cb7e80b80df5f26cdda9ba3ecde2c9decf..8d030c2bbe42adb0d216d5aa0b678933af4af722 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" package="com.example.mindrover"> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> diff --git a/app/src/main/java/com/example/mindrover/ConnectionFragment.kt b/app/src/main/java/com/example/mindrover/ConnectionFragment.kt index 8b6dad0a736079e2c4ef39e7b8f834b50433d75c..bdc048a6d11c8af0121c51e0715f4e06dc14ec31 100644 --- a/app/src/main/java/com/example/mindrover/ConnectionFragment.kt +++ b/app/src/main/java/com/example/mindrover/ConnectionFragment.kt @@ -1,10 +1,15 @@ package com.example.mindrover +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer @@ -13,7 +18,6 @@ import com.example.mindrover.databinding.FragmentConnectionBinding import com.example.mindrover.model.ConnectionState import com.example.mindrover.model.EEGDataListener - class ConnectionFragment : Fragment() { private val sharedViewModel: EEGDataListener by activityViewModels() @@ -49,7 +53,15 @@ class ConnectionFragment : Fragment() { super.onViewCreated(view, savedInstanceState) binding.button.setOnClickListener { - sharedViewModel.establishConnection() +// sharedViewModel.establishConnection() + binding.pBar.visibility = View.VISIBLE + if ((activity as MainActivity?)!!.isNetworkAvailable()) { + findNavController().navigate(R.id.action_connectionFragment_to_eegSignalFragment) + Toast.makeText((activity as MainActivity?)!!,"Connection success", Toast.LENGTH_SHORT).show() + (activity as MainActivity?)!!.startConnection() + } else { + Toast.makeText((activity as MainActivity?)!!,"Hello", Toast.LENGTH_SHORT).show() + } } val nameObserver = Observer<ConnectionState> { state -> diff --git a/app/src/main/java/com/example/mindrover/EegSignalFragment.kt b/app/src/main/java/com/example/mindrover/EegSignalFragment.kt index be562e3caff8b03e96fc2d8475fde1a4dfd2498a..29b142bf3c7a8e0b2016dd6dcf9db7e628fc1a60 100644 --- a/app/src/main/java/com/example/mindrover/EegSignalFragment.kt +++ b/app/src/main/java/com/example/mindrover/EegSignalFragment.kt @@ -1,11 +1,15 @@ package com.example.mindrover import android.os.Bundle +import android.util.Log import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels + import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle @@ -14,6 +18,7 @@ import com.example.mindrover.databinding.FragmentEegSignalBinding import com.example.mindrover.model.EEGDataListener import com.jjoe64.graphview.series.DataPoint import com.jjoe64.graphview.series.LineGraphSeries +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch /** @@ -24,7 +29,7 @@ import kotlinx.coroutines.launch class EegSignalFragment : Fragment() { - private val eegListener: EEGDataListener by viewModels() + private val eegListener: EEGDataListener by activityViewModels() lateinit var graphSeries: Array<LineGraphSeries<DataPoint>> @@ -45,14 +50,32 @@ class EegSignalFragment : Fragment() { super.onCreate(savedInstanceState) graphSeries = arrayOf() - initGraphSeries(6) + initGraphSeries(eegListener.numOfChannels) + +// lifecycleScope.launch { +// +// lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { +// eegListener.latestData.collect { +// for (i in 0..it.size-1) { +//// graphSeries[i].resetData(it[i]) +// } +// if (run) { +//// adapter.updateList(graphSeries) +// } +// } +// } +// } lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - eegListener.latestData.collect { + + eegListener.sharedFlow.collect { for (i in 0..it.size-1) { graphSeries[i].resetData(it[i]) +// Toast.makeText((activity as MainActivity?)!!,"Data Recieved ${it.size}", Toast.LENGTH_SHORT).show() + Log.d("Data reciever", "Data Recieved ${it.size}") + } if (run) { adapter.updateList(graphSeries) @@ -82,7 +105,14 @@ class EegSignalFragment : Fragment() { // in content do not change the layout size of the RecyclerView recyclerView.setHasFixedSize(true) + binding.testButton.setOnClickListener { + eegListener.addData(arrayOf(20.1, 20.1, 20.1, 20.1, 20.1, 20.1, 20.1, 20.1)) + Toast.makeText((activity as MainActivity?)!!,"Button press", Toast.LENGTH_SHORT).show() + eegListener.addImpedance(arrayOf(12,12,12,12,12,1,3,2)) + + } + } +} -} \ No newline at end of file diff --git a/app/src/main/java/com/example/mindrover/GameFragment.kt b/app/src/main/java/com/example/mindrover/GameFragment.kt index 78f985d2c36df013f0b4e77a7923654fbc11a9fe..8830406e00b58b0c35bcd3920ccd729214e3a442 100644 --- a/app/src/main/java/com/example/mindrover/GameFragment.kt +++ b/app/src/main/java/com/example/mindrover/GameFragment.kt @@ -1,10 +1,23 @@ package com.example.mindrover import android.os.Bundle +import android.util.Log import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController +import com.example.mindrover.databinding.FragmentGameBinding +import com.example.mindrover.databinding.FragmentTrainBinding +import com.example.mindrover.model.EEGDataListener +import com.example.mindrover.model.ExperimentCommand +import com.example.mindrover.model.ExperimentNavigator +import com.example.mindrover.model.ExperimentState +import com.example.mindrover.model.GameCommand +import com.example.mindrover.model.GameManager // TODO: Rename parameter arguments, choose names that match // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER @@ -16,44 +29,83 @@ private const val ARG_PARAM2 = "param2" * Use the [GameFragment.newInstance] factory method to * create an instance of this fragment. */ + + + class GameFragment : Fragment() { - // TODO: Rename and change types of parameters - private var param1: String? = null - private var param2: String? = null + + private val gameManager: GameManager by viewModels() + private val eegDataListener: EEGDataListener by activityViewModels() + + private lateinit var binding: FragmentGameBinding + + private var experimentRuns = false + private var radioGroupArray = arrayOf<Int>() + private var choiceNum = 0 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - arguments?.let { - param1 = it.getString(ARG_PARAM1) - param2 = it.getString(ARG_PARAM2) - } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_game, container, false) +// binding = FragmentTrainBinding.inflate(inflater) + binding = FragmentGameBinding.inflate(inflater) +// binding.imageView3.setImageResource(R.drawable.ic_arrow_circle_left) +// binding.startTrainTexview.setText(R.string.left_command) + + binding.radioButton3.setText("Jeromos") + binding.radioGroup.check(binding.radioButton5.id) +// binding.radioButton4.checked + radioGroupArray = arrayOf(binding.radioButton3.id, binding.radioButton4.id, + binding.radioButton5.id, binding.radioButton6.id) + return binding.root } - companion object { - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @param param1 Parameter 1. - * @param param2 Parameter 2. - * @return A new instance of fragment GameFragment. - */ - // TODO: Rename and change types and number of parameters - @JvmStatic - fun newInstance(param1: String, param2: String) = - GameFragment().apply { - arguments = Bundle().apply { - putString(ARG_PARAM1, param1) - putString(ARG_PARAM2, param2) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + experimentRuns = true + + val commandObserver = Observer<GameCommand> { state -> + if (experimentRuns) { + if (state == GameCommand.GO_UP) { + choiceNum += 1 + if (choiceNum > 3) { + choiceNum = 0 + } + } else if (state == GameCommand.GO_DOWN) { + choiceNum -= 1 + if (choiceNum < 0) { + choiceNum = 3 + } } + + binding.radioGroup.check(radioGroupArray[choiceNum]) + + Log.d("Observer", "Observed:${state.name}") } + } +// eegDataListener.actualCommand.observe(viewLifecycleOwner, commandObserver) + gameManager.actualCommand.observe(viewLifecycleOwner, commandObserver) + +// val accelerationObserver = Observer<Array<Int>> { +// +// binding.radioButton4.setText(it[0].toString()) +// binding.radioButton5.setText(it[1].toString()) +// binding.radioButton6.setText(it[2].toString()) +// +// } + +// eegDataListener.accelerations.observe(viewLifecycleOwner, accelerationObserver) + + gameManager.setEEGDataListener(eegDataListener) + gameManager.startGame(5) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/mindrover/ImpedanceCheckerFragment.kt b/app/src/main/java/com/example/mindrover/ImpedanceCheckerFragment.kt index 47f8d4198208a1798cb629697817a1a7d5e00837..4103104395172563e9fad70dcd0a3b32019f6870 100644 --- a/app/src/main/java/com/example/mindrover/ImpedanceCheckerFragment.kt +++ b/app/src/main/java/com/example/mindrover/ImpedanceCheckerFragment.kt @@ -6,6 +6,7 @@ import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.Observer @@ -20,8 +21,8 @@ import kotlinx.coroutines.launch class ImpedanceCheckerFragment : Fragment() { - private val eegListener: EEGDataListener by viewModels() - lateinit var impedances: Array<Double> + private val eegListener: EEGDataListener by activityViewModels() + lateinit var impedances: Array<Int> private lateinit var binding: FragmentImpedanceCheckerBinding lateinit var adapter: ImpedanceAdapter @@ -43,7 +44,7 @@ class ImpedanceCheckerFragment : Fragment() { val recyclerView = binding.impedanceRecycler - impedances = arrayOf(5.0, 6.0, 7.0, 8.0, 9.0, 10.0) + impedances = arrayOf(5, 6, 7, 8, 9, 10) adapter = ImpedanceAdapter(requireContext(), impedances) recyclerView.adapter = adapter @@ -52,7 +53,7 @@ class ImpedanceCheckerFragment : Fragment() { // recyclerView.setHasFixedSize(true) - val impedanceObserver = Observer<Array<Double>> { state -> + val impedanceObserver = Observer<Array<Int>> { state -> impedances = state adapter.updateList(impedances) } @@ -61,5 +62,4 @@ class ImpedanceCheckerFragment : Fragment() { } - } \ No newline at end of file diff --git a/app/src/main/java/com/example/mindrover/MainActivity.kt b/app/src/main/java/com/example/mindrover/MainActivity.kt index 4819677ee11621222ae75fd9f917ed7fea3e0def..0bc7f89da262d0dc8182055846ffd11f93195ab0 100644 --- a/app/src/main/java/com/example/mindrover/MainActivity.kt +++ b/app/src/main/java/com/example/mindrover/MainActivity.kt @@ -2,26 +2,64 @@ package com.example.mindrover import android.Manifest import android.app.Activity +import android.content.Context +import android.content.Intent import android.content.pm.PackageManager +import android.net.ConnectivityManager +import android.net.NetworkCapabilities import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.provider.Settings import android.util.Log import android.view.View +import androidx.activity.result.contract.ActivityResultContracts import androidx.core.app.ActivityCompat -import androidx.fragment.app.Fragment import androidx.navigation.NavController -import androidx.navigation.Navigation import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.ui.NavigationUI import androidx.navigation.ui.NavigationUI.setupWithNavController import com.example.mindrover.databinding.ActivityMainBinding +import com.example.mindrover.model.EEGDataListener import com.google.android.material.bottomnavigation.BottomNavigationView +import mylibrary.mindrove.SensorData +import mylibrary.mindrove.ServerManager +import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch +import mylibrary.mindrove.Instruction class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var navController: NavController + private val eegListener: EEGDataListener by viewModels() + private val serverManager = ServerManager { sensorData: SensorData -> +// Update the sensor data text +// sensorDataText.postValue(sensorData.accelerationX.toString()) + if (isEEGMode) { + eegListener.addData( + arrayOf( + sensorData.channel1, sensorData.channel2, + sensorData.channel3, sensorData.channel4, sensorData.channel5, + sensorData.channel6, sensorData.channel7, sensorData.channel8 + ) + ) +// eegListener.addGeolocation(sensorData.accelerationX, sensorData.accelerationY, sensorData.accelerationZ) + + } else { + eegListener.addImpedance( + arrayOf( + sensorData.impedance1To2, + sensorData.impedance2To3, sensorData.impedance3To4, sensorData.impedance5To4, + sensorData.impedance5To6, sensorData.impedance1ToDRL, sensorData.impedance3ToDRL, + sensorData.impedance6ToRef, sensorData.impedanceRefTo4, sensorData.impedanceRefToDRL + ) + ) + } +// Log.d("Connection success", "Data recieved${sensorData.channel6} - ${sensorData.channel7} - ${sensorData.channel8}") + } + private var isServerManagerStarted = false + private var isEEGMode = true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) @@ -37,16 +75,74 @@ class MainActivity : AppCompatActivity() { if(destination.id == R.id.trainFragment || destination.id == R.id.connectionFragment) { bottomNavigationView.visibility = View.GONE } else { - bottomNavigationView.visibility = View.VISIBLE } + if (destination.id == R.id.impedanceCheckerFragment) { +// serverManager.stop() + lifecycleScope.launch { + isEEGMode = false +// serverManager.stop() +// serverManager.start() + serverManager.sendInstruction(Instruction.IMP) + +// while (!serverManager.isMessageReceived) { +// delay(300L) +// Log.d("Message NOT recieved", "EEGMode continue") +// } +// Log.d("Message recieved", "EEGMode off") + } +// serverManager.start() + } else if(!isEEGMode) { + lifecycleScope.launch { +// serverManager.start() + serverManager.sendInstruction(Instruction.EEG) +// if (serverManager.isMessageReceived) { +// isEEGMode = true +// } + } + } } - - +// if (savedInstanceState != null) { +// startConnection() +// } } + override fun onDestroy() { + super.onDestroy() + // Stop the server when the activity is destroyed + serverManager.stop() + } + // Function to check network connectivity + fun isNetworkAvailable(): Boolean { + val connectivityManager = + getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val network = connectivityManager.activeNetwork + val capabilities = connectivityManager.getNetworkCapabilities(network) + return capabilities != null && + (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) + } + + fun startConnection() { + if (!isServerManagerStarted) { + serverManager.start() + isServerManagerStarted = true + Log.d("ServerManager", "ServerManager started") + } + } + var isWifiSettingsOpen = false + private val wifiSettingsLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { +// This block is executed when the Wi-Fi settings activity is finished + isWifiSettingsOpen = false + } + // Function to open Wi-Fi settings + private fun openWifiSettings() { + val intent = Intent(Settings.ACTION_WIFI_SETTINGS) + wifiSettingsLauncher.launch(intent) + } } diff --git a/app/src/main/java/com/example/mindrover/TrainFragment.kt b/app/src/main/java/com/example/mindrover/TrainFragment.kt index ad6fb383996866dedc006017cd090ffea4c956e4..9bad5389a6a6c5bc05002ed611eadc5317935e40 100644 --- a/app/src/main/java/com/example/mindrover/TrainFragment.kt +++ b/app/src/main/java/com/example/mindrover/TrainFragment.kt @@ -6,8 +6,12 @@ import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import com.example.mindrover.databinding.FragmentImpedanceCheckerBinding import com.example.mindrover.databinding.FragmentTrainBinding @@ -15,12 +19,13 @@ import com.example.mindrover.model.EEGDataListener import com.example.mindrover.model.ExperimentCommand import com.example.mindrover.model.ExperimentNavigator import com.example.mindrover.model.ExperimentState +import kotlinx.coroutines.launch import kotlin.math.exp class TrainFragment : Fragment() { private val experimentNavigator: ExperimentNavigator by viewModels() - private val eegDataListener: EEGDataListener by viewModels() + private val eegDataListener: EEGDataListener by activityViewModels() private lateinit var binding: FragmentTrainBinding @@ -75,7 +80,7 @@ class TrainFragment : Fragment() { - val impedanceObserver = Observer<Array<Double>> { state -> + val impedanceObserver = Observer<Array<Int>> { state -> Log.d("HEllo", "${state.size}") if (state.size > 0) binding.startTrainTexview.setText("${state[0]}") @@ -83,7 +88,8 @@ class TrainFragment : Fragment() { eegDataListener.impedances.observe(viewLifecycleOwner, impedanceObserver) - experimentNavigator.startExperiment(5) + experimentNavigator.setEEGListener(eegDataListener) + experimentNavigator.startExperiment(3) } diff --git a/app/src/main/java/com/example/mindrover/TrainHomeFragment.kt b/app/src/main/java/com/example/mindrover/TrainHomeFragment.kt index 1be43a25f3afd50a8c12d6fb24a1089fe908bb8b..cee3585d28fd9e6aeffb291877b1fba87d7f64ed 100644 --- a/app/src/main/java/com/example/mindrover/TrainHomeFragment.kt +++ b/app/src/main/java/com/example/mindrover/TrainHomeFragment.kt @@ -5,6 +5,7 @@ import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController @@ -18,7 +19,7 @@ class TrainHomeFragment : Fragment() { private lateinit var binding: FragmentTrainHomeBinding private val experimentNavigator: ExperimentNavigator by viewModels() - private val eegDataListener: EEGDataListener by viewModels() + private val eegDataListener: EEGDataListener by activityViewModels() diff --git a/app/src/main/java/com/example/mindrover/adapter/ImpedanceAdapter.kt b/app/src/main/java/com/example/mindrover/adapter/ImpedanceAdapter.kt index 43c7d2c377d9ccd73d1182bd734b72a9f22c10df..0384575eda48030020be82dcaaea2e2d49c5c4c7 100644 --- a/app/src/main/java/com/example/mindrover/adapter/ImpedanceAdapter.kt +++ b/app/src/main/java/com/example/mindrover/adapter/ImpedanceAdapter.kt @@ -2,6 +2,7 @@ package com.example.mindrover.adapter import android.annotation.SuppressLint import android.content.Context +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -12,7 +13,7 @@ import kotlin.math.roundToInt class ImpedanceAdapter( private val context: Context, - private var dataset: Array<Double> + private var dataset: Array<Int> ): RecyclerView.Adapter<ImpedanceAdapter.ImpedanceViewHolder>() { // Provide a reference to the views for each data item @@ -38,16 +39,18 @@ class ImpedanceAdapter( * Replace the contents of a view (invoked by the layout manager) */ override fun onBindViewHolder(holder: ImpedanceViewHolder, position: Int) { - val impedanceValue:Double = (dataset[position]*100).roundToInt().toDouble() / 100 +// val impedanceValue:Double = (dataset[position]*100).roundToInt().toDouble() / 100 + val impedanceValue:Double = (dataset[position]*100).toDouble() / 100 holder.channelText.setText("Channel ${position}:") holder.impedanceText.setText("${impedanceValue} Ohm") } @SuppressLint("NotifyDataSetChanged") - fun updateList(itemList: Array<Double>) { + fun updateList(itemList: Array<Int>) { dataset = itemList notifyDataSetChanged() + Log.v("Adapter", "ind: " + dataset[0]) } /** diff --git a/app/src/main/java/com/example/mindrover/model/EEGDataListener.kt b/app/src/main/java/com/example/mindrover/model/EEGDataListener.kt index e9720e37c907529e4a3a83dd0d09fd281720534a..15d016460539beb6c3bdb7426afe69d405e0352c 100644 --- a/app/src/main/java/com/example/mindrover/model/EEGDataListener.kt +++ b/app/src/main/java/com/example/mindrover/model/EEGDataListener.kt @@ -5,18 +5,10 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import brainflow.BoardIds -import brainflow.BoardShim -import brainflow.BrainFlowInputParams -import brainflow.BrainFlowPresets -import com.example.mindrover.R import com.jjoe64.graphview.series.DataPoint -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch -import kotlin.random.Random - enum class ConnectionState { NO_CONNECTION, PENDING, CONNECTION @@ -31,102 +23,101 @@ class EEGDataListener: ViewModel() { private val _lastData = MutableLiveData<Array<Array<DataPoint>>>() val lastData: LiveData<Array<Array<DataPoint>>> = _lastData - private val _impedances = MutableLiveData<Array<Double>>() - val impedances: LiveData<Array<Double>> = _impedances + private val _impedances = MutableLiveData<Array<Int>>() + val impedances: LiveData<Array<Int>> = _impedances +// private val _accelerations = MutableLiveData<Array<Int>>() +// val accelerations: LiveData<Array<Int>> = _impedances +// private val _connection = MutableLiveData<ConnectionState>() val connection: LiveData<ConnectionState> = _connection var test_connection_num = 0 - val numOfChannels = 6 + val numOfChannels = 8 var time: Double = 1.0 - val MAXSTOREDPOINT = 1000 + val MAXSTOREDPOINT = 2000 private var running: Boolean = false + val _sharedFlow = MutableSharedFlow<Array<Array<DataPoint>>>() + val sharedFlow = _sharedFlow.asSharedFlow() - val latestData: Flow<Array<Array<DataPoint>>> = flow { - - -// fun startStreaming() { - -// viewModelScope.launch { - - var currentData = arrayOf<Array<DataPoint>>() - for (i in 0 until numOfChannels) { - wholeData = wholeData.plus(arrayOf<DataPoint>()) - currentData = currentData.plus(arrayOf<DataPoint>()) - } - - _impedances.value = emptyArray() - - repeat(numOfChannels) { - _impedances.value = _impedances.value?.plus(Random.nextDouble()*20+2) - } - - - - while (true) { - - //Get last package of data - for (i in 0..5) { - time++ - - for (ch in 0 until numOfChannels) { - if ((time.toInt() % 4000) == 20) { - wholeData[ch] = wholeData[ch].plus(DataPoint(time, 20.0)) - } - else { - wholeData[ch] = - wholeData[ch].plus(DataPoint(time, Random.nextDouble() * 10)) - } - } - } - - //Update dataset - var imps = _impedances.value!! - for (i in 0 until numOfChannels) { - imps[i] = imps[i] + (Random.nextDouble() - 0.5) - } - _impedances.value = imps - - delay(100) - - //Update impedances - val size = minOf(100, wholeData[0].size) - - for (ch in 0 until numOfChannels) { - val data = wholeData[ch].takeLast(size).toTypedArray() - for (i in 0 until size) { - data[i] = DataPoint(i.toDouble(), data[i].y) - } - currentData[ch] = data - } - - //Free memory with whole data - - if (wholeData[0].size > MAXSTOREDPOINT*2) { - for (ch in 0 until numOfChannels) { - wholeData[ch] = wholeData[ch].takeLast(MAXSTOREDPOINT).toTypedArray() - } - } - - _lastData.value = currentData - emit(currentData) - - } +// val latestData: Flow<Array<Array<DataPoint>>> = flow { +// +// +//// fun startStreaming() { +// +//// viewModelScope.launch { +// +// var currentData = arrayOf<Array<DataPoint>>() +// for (i in 0 until numOfChannels) { +// wholeData = wholeData.plus(arrayOf<DataPoint>()) +// currentData = currentData.plus(arrayOf<DataPoint>()) +// } +// +// _impedances.value = emptyArray() +// +//// repeat(numOfChannels) { +//// _impedances.value = _impedances.value?.plus(Random.nextDouble() * 20 + 2) +//// } +// +// +// +// while (true) { +// +// //Get last package of data +// for (i in 0..5) { +// time++ +// +// for (ch in 0 until numOfChannels) { +// if ((time.toInt() % 4000) == 20) { +// wholeData[ch] = wholeData[ch].plus(DataPoint(time, 20.0)) +// } else { +// wholeData[ch] = +// wholeData[ch].plus(DataPoint(time, Random.nextDouble() * 10)) +// } +// } +// } +// +// //Update dataset +//// var imps = _impedances.value!! +//// for (i in 0 until numOfChannels) { +//// imps[i] = imps[i] + (Random.nextDouble() - 0.5) +//// } +//// _impedances.value = imps +// +// delay(100) +// +// //Update impedances +// val size = minOf(100, wholeData[0].size) +// +// for (ch in 0 until numOfChannels) { +// val data = wholeData[ch].takeLast(size).toTypedArray() +// for (i in 0 until size) { +// data[i] = DataPoint(i.toDouble(), data[i].y) +// } +// currentData[ch] = data +// } +// +// //Free memory with whole data +// +// if (wholeData[0].size > MAXSTOREDPOINT * 2) { +// for (ch in 0 until numOfChannels) { +// wholeData[ch] = wholeData[ch].takeLast(MAXSTOREDPOINT).toTypedArray() +// } +// } +// +// _lastData.value = currentData +// emit(currentData) +// +// } // } - } - - -// val testData: Flow<Array<DataPoint>> = flow { -// testCoroutine() // } init { @@ -135,63 +126,82 @@ class EEGDataListener: ViewModel() { // establishConnection() } - fun establishConnection() { + fun getLatestData(ms: Int): Array<Array<Double>>? { - viewModelScope.launch { - try { + if (wholeData.isEmpty() || wholeData[0].size < ms) { + Log.e("ERROR", "wholedata is shorter than expected") + return null + } + var data: Array<Array<Double>> = arrayOf() + for (ch in 0 until numOfChannels) { + var tmpArray: Array<Double> = arrayOf() + for (dp :DataPoint in wholeData[ch].takeLast(ms).toTypedArray()) { + tmpArray = tmpArray.plus(dp.y) + } +// data = data.plus(arrayOf(wholeData[ch].takeLast(ms).toTypedArray())) + data = data.plus(tmpArray) + } + return data + } - val par = BrainFlowInputParams() - par.set_ip_protocol(22) - par.ip_address - val shim = BoardShim(38, par) -// val shim = BoardShim(BoardIds.PIEEG_BOARD, BrainFlowInputParams()) + fun addData(array: Array<Double>) { +// var ch = 0 +// for (e in array) { +// wholeData[ch].plus(DataPoint(time,e)) +// ch++ +// } + //time++ -// try { -// BoardShim.enable_board_logger() + var currentData = arrayOf<Array<DataPoint>>() -// } catch (e: Exception) { -// Log.d("EEGException","Connection enabled fail") -// } -// BoardShim.get_eeg_channels(10) -// BoardShim.enable_board_logger() + val empt = wholeData.size == 0 + for (i in 0 until numOfChannels) { + currentData = currentData.plus(arrayOf<DataPoint>()) + if (empt) { + wholeData = wholeData.plus(arrayOf<DataPoint>()) + } + } - Log.d("HEllo", "BoardId:${BoardIds.PIEEG_BOARD}") -// try { -// val shim = BoardShim(BoardIds.PIEEG_BOARD, BrainFlowInputParams()) -// println(shim.get_board_data().size) -// } catch (e: Exception) { -// -// } + time++ + val size = minOf(1000, wholeData[0].size) + for (ch in 0 until numOfChannels) { + wholeData[ch] = + wholeData[ch].plus(DataPoint(time, array[ch])) - Log.d("EEGConnection", "Ip address: ${par.ip_protocol}") + val data = wholeData[ch].takeLast(size).toTypedArray() + for (i in 0 until size) { + data[i] = DataPoint(i.toDouble(), data[i].y) + } + currentData[ch] = data -// Log.d("Connection", "Create dsocket") -// val port = 4210 -// val dsocket = DatagramSocket(port) -// -// Log.d("Connection", "------------------Created dsocket--------------------") -// var serverAddress = InetAddress.getByName("192.168.4.1") + } + if (time.toInt()%100 == 0) { + viewModelScope.launch { + _sharedFlow.emit(currentData) + } + } + //Free memory with whole data + if (wholeData[0].size > MAXSTOREDPOINT * 2) { + for (ch in 0 until numOfChannels) { + wholeData[ch] = wholeData[ch].takeLast(MAXSTOREDPOINT).toTypedArray() + } + } + } - _connection.value = ConnectionState.PENDING + fun addImpedance(array: Array<Int>) { +// _impedances.value = array + Log.d("Impedance arrived", "Impedance value 1: ${array[5]}") + } - delay(2000) - test_connection_num++ - if (test_connection_num%2 == 1) { - _connection.value = ConnectionState.CONNECTION -// startStreaming() - } else { - _connection.value = ConnectionState.NO_CONNECTION - } +// fun addGeolocation(x: Int, y: Int, z: Int) { +// _accelerations.value = arrayOf(x, y, z) +// } - } catch (e: Exception) { - } - } - } } diff --git a/app/src/main/java/com/example/mindrover/model/ExperimentNavigator.kt b/app/src/main/java/com/example/mindrover/model/ExperimentNavigator.kt index 67f761c41696fa1e891ad70ed35322d74ffcd807..c6c0dad0a1ab51f3e658b8c7a3a85cfff2f8aae4 100644 --- a/app/src/main/java/com/example/mindrover/model/ExperimentNavigator.kt +++ b/app/src/main/java/com/example/mindrover/model/ExperimentNavigator.kt @@ -1,24 +1,50 @@ package com.example.mindrover.model import android.util.Log +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewModelScope import com.example.mindrover.R +import com.jjoe64.graphview.series.DataPoint import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.apache.commons.lang3.ObjectUtils.Null data class ExperimentCommand(val id: String, val imgId:Int, val textId:Int) +data class LabeledData(val label: String, val data: Array<Array<Double>>) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as LabeledData + + if (label != other.label) return false + if (!data.contentDeepEquals(other.data)) return false + + return true + } + + override fun hashCode(): Int { + var result = label.hashCode() + result = 31 * result + data.contentDeepHashCode() + return result + } +} + enum class ExperimentState { PENDING, STARTED, FINISHED } -class ExperimentNavigator: ViewModel() { +class ExperimentNavigator(): ViewModel() { private val _actualCommand = MutableLiveData<ExperimentCommand>() val actualCommand: LiveData<ExperimentCommand> = _actualCommand @@ -29,12 +55,17 @@ class ExperimentNavigator: ViewModel() { private val _experimentRun = MutableLiveData<Int>() val experimentRun: LiveData<Int> = _experimentRun - + private var _labeledDataArray: Array<LabeledData> = arrayOf() + private var _eegDataListener: EEGDataListener? = null init { _doesExperimentRun.value = ExperimentState.PENDING } + fun setEEGListener(eegDataListener: EEGDataListener) { + _eegDataListener = eegDataListener + } + fun startExperiment(num: Int) { @@ -51,8 +82,20 @@ class ExperimentNavigator: ViewModel() { commands.shuffle() + _doesExperimentRun.value = ExperimentState.STARTED +// viewModelScope.launch { +// +// while(_doesExperimentRun.value == ExperimentState.STARTED) { +// _eegDataListener?.latestData?.collect { +// Log.d("Data recieved", "A datapacket is recieved ${it.size} - ${it[0].size} - ${it[0][0]?.y}") +// for (i in 0..it.size - 1) { +// } +// } +// } +// } + viewModelScope.launch { - _doesExperimentRun.value = ExperimentState.STARTED + for (comm in commands) { Log.d("Experiment", comm.id) _actualCommand.value = comm @@ -63,6 +106,13 @@ class ExperimentNavigator: ViewModel() { _actualCommand.value = action delay(2000) //TODO: Save data + + val lastArray = _eegDataListener?.getLatestData(50) +// val lastArray = _eegDataListener.latestData.collect() + + if (lastArray != null) { + _labeledDataArray = _labeledDataArray.plus(LabeledData(comm.id, lastArray)) + } } _doesExperimentRun.value = ExperimentState.FINISHED diff --git a/app/src/main/java/com/example/mindrover/model/GameManager.kt b/app/src/main/java/com/example/mindrover/model/GameManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..b6180b1ac76a9d5581862a391dfae400265c5f76 --- /dev/null +++ b/app/src/main/java/com/example/mindrover/model/GameManager.kt @@ -0,0 +1,65 @@ +package com.example.mindrover.model + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.jjoe64.graphview.series.DataPoint +import kotlinx.coroutines.Delay +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.jetbrains.kotlinx.dl.dataset.Dataset + +enum class GameCommand { + CHOOSE, REST, GO_UP, GO_DOWN +} + +class GameManager(): ViewModel() { + + var network: NeuralNetwork? = null + private var _eegDataListener: EEGDataListener? = null + + private val _actualCommand = MutableLiveData<GameCommand>() + val actualCommand: LiveData<GameCommand> = _actualCommand + + var debugNum = 1 + + + fun nextCommand(eeg: Array<Array<Double>>, pos: Array<Array<Double>>) { +// eegDataset = Dataset() +// network.predict(eeg) + debugNum += 1 + if (debugNum%3 == 1) { + _actualCommand.value = GameCommand.GO_DOWN + } else if (debugNum%3 == 2) { + _actualCommand.value = GameCommand.GO_DOWN + } else { + _actualCommand.value = GameCommand.CHOOSE + } + } + + fun setEEGDataListener(eegListener: EEGDataListener) { + _eegDataListener = eegListener + } + + fun startGame(numOfQuestions: Int) { + + viewModelScope.launch { + + for (i in 0..numOfQuestions) { + _actualCommand.value = GameCommand.REST + while (actualCommand.value != GameCommand.CHOOSE) { + + delay(500) + var eeg = _eegDataListener?.getLatestData(50) + var pos = _eegDataListener?.getLatestData(50) + + if (eeg != null && pos != null) { + nextCommand(eeg, pos) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mindrover/model/NeuralNetwork.kt b/app/src/main/java/com/example/mindrover/model/NeuralNetwork.kt index 2a5aadf0d3c3a95862326c141b8886d4bd499125..1f6a8396711ec6ea9bde7f0597506d32a0f40135 100644 --- a/app/src/main/java/com/example/mindrover/model/NeuralNetwork.kt +++ b/app/src/main/java/com/example/mindrover/model/NeuralNetwork.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlinx.dl.api.core.layer.core.Dense import org.jetbrains.kotlinx.dl.api.core.layer.core.Input import org.jetbrains.kotlinx.dl.api.core.layer.pooling.AvgPool2D import org.jetbrains.kotlinx.dl.api.core.layer.reshaping.Flatten +import org.jetbrains.kotlinx.dl.dataset.Dataset import org.jetbrains.kotlinx.dl.dataset.OnHeapDataset import org.jetbrains.kotlinx.dl.dataset.handler.NUMBER_OF_CLASSES import org.jetbrains.kotlinx.dl.dataset.mnist @@ -102,6 +103,10 @@ class NeuralNetwork { } + fun predict(dataset: Dataset): List<Int> { + return model.predict(dataset) + } + fun saveNeuralNetwork(name: String) { // model.save('keras-cifar-10/weights', savingFormat='h5') // diff --git a/app/src/main/res/layout/fragment_eeg_signal.xml b/app/src/main/res/layout/fragment_eeg_signal.xml index 35824853e87eceb5da1f56a38c24c2f9734105d3..8a830e890871381141172533c89844101b1215ce 100644 --- a/app/src/main/res/layout/fragment_eeg_signal.xml +++ b/app/src/main/res/layout/fragment_eeg_signal.xml @@ -13,4 +13,10 @@ android:scrollbars="vertical" app:layoutManager="LinearLayoutManager" /> + + <Button + android:id="@+id/testButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> </FrameLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_game.xml b/app/src/main/res/layout/fragment_game.xml index f1ce432c571363b1e3b5bb8cf76c997a76b17cc8..65776f1570d8ddad702f5e914cafbe75d0f2c8f7 100644 --- a/app/src/main/res/layout/fragment_game.xml +++ b/app/src/main/res/layout/fragment_game.xml @@ -26,7 +26,8 @@ <RadioGroup android:layout_width="match_parent" - android:layout_height="278dp"> + android:layout_height="278dp" + android:id="@+id/radioGroup"> <RadioButton android:id="@+id/radioButton3" diff --git a/build.gradle b/build.gradle index fcb2eae005508b6d6063f06f58062382afac1fa5..7e3d2f384f0e8fdfd68b23760c341b2cb9db440b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.2.1' apply false - id 'com.android.library' version '7.2.1' apply false - id 'org.jetbrains.kotlin.android' version '1.7.10' apply false + id 'com.android.application' version '8.3.2' apply false + id 'com.android.library' version '8.3.2' apply false + id 'org.jetbrains.kotlin.android' version '1.9.0' apply false } task clean(type: Delete) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5b7d7163adf90bba36e57eb6c76044fd44cf46ba..88a1cbf08ceec17c81cb6b881cb5c78c0465d35b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Sep 05 16:25:15 CEST 2022 +#Fri May 03 15:54:22 CEST 2024 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists