如何在 kotlin 上的 MPAndroidChart 中绘制从蓝牙模块接收到的实时传感器值?

发布于 2025-01-11 07:15:32 字数 7038 浏览 0 评论 0原文

我有一个传感器,通过将其连接到 Arduino 板,我可以在 Arduino IDE 上以图表的形式在串行绘图仪中看到传感器信号。
我想通过蓝牙模块在我的APP上实时看到相同的信号。
我正在使用 MPAndroidChart 库在 Kotlin 中绘制图表。
要使用 MPAndroidChart 绘制图表以及有关如何使用 MPAndroidChart 的更多信息,
我通过Github中的以下链接查看了Google Play中发布的示例程序代码 https://github.com/PhilJay/MPAndroidChart
并将 Java 代码转换为 Kotlin,现在我可以用随机数绘制图表。
但我计划使用通过蓝牙接收的传感器数据绘制自己的图表。
我编写了一个 ReceiveData 函数,我想用此方法获取数据并将其提供给 addEntry 函数,这样我就可以绘制我的图,而不是用随机数绘制图表。带有蓝牙数据的图表。
但我不知道该怎么办。
通过调用feedMultiple函数

btn_startTest.setOnClickListener { feedMultiple() }

并在addEntry中使用以下代码绘制随机数的图形。

data.addEntry(Entry(set.entryCount.toFloat(), (Math.random() * 40).toFloat() + 30f), 0)

但我遇到的问题是,我不知道如何将通过蓝牙接收到的数据传输addEntry函数并使用它们而不是随机数。
这是我的应用程序的完整代码:

class ElectromyographyAnalysis : AppCompatActivity(), OnChartValueSelectedListener {

    companion object {
        val TAG = "EMGSensor"
        val APP_NAME = "EMGSensor"

        var m_myUUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb")
        var m_bluetoothSocket: BluetoothSocket? = null
        lateinit var m_progress: ProgressDialog
        lateinit var m_bluetoothAdapter: BluetoothAdapter
        var m_isConnected: Boolean = false
        lateinit var m_address: String

        var xVal: Int = 0
        var yVal: Int = 0
    }

    lateinit var emgChart: LineChart


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.electromyography_analysis_layout)

        title = "Electromyography Analysis"

        m_address = intent.getStringExtra(SelectDeviceActivity.EXTRA_ADDRESS).toString()

        ConnectToDevice(this).execute()

        //add this new
        window.setFlags(
            WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN
        )


        emgChart = findViewById(R.id.emg_lineChart)
        emgChart.setOnChartValueSelectedListener(this)

        // enable description text
        emgChart.description.isEnabled = true

        // enable touch gestures
        emgChart.setTouchEnabled(true)

        // enable scaling and dragging

        // enable scaling and dragging
        emgChart.isDragEnabled = true
        emgChart.setScaleEnabled(true)
        emgChart.setDrawGridBackground(false)

        // if disabled, scaling can be done on x- and y-axis separately
        emgChart.setPinchZoom(true)

        // set an alternative background color
        emgChart.setBackgroundColor(Color.LTGRAY)

        val data = LineData()
        data.setValueTextColor(Color.WHITE)

        //add empty data

        // add empty data
        emgChart.data = data

        // get the legend (only possible after setting data)

        // get the legend (only possible after setting data)
        val l: Legend = emgChart.legend

        // modify the legend ...

        // modify the legend ...
        l.form = LegendForm.LINE
        //l.typeface =
        l.textColor = Color.WHITE

        val xl: XAxis = emgChart.xAxis
        //xl.typeface = tfLight
        xl.textColor = Color.WHITE
        xl.setDrawGridLines(false)
        xl.setAvoidFirstLastClipping(true)
        xl.isEnabled = true

        val leftAxis: YAxis = emgChart.getAxisLeft()
        //leftAxis.typeface = tfLight
        leftAxis.textColor = Color.WHITE
        leftAxis.axisMaximum = 100f
        leftAxis.axisMinimum = 0f
        leftAxis.setDrawGridLines(true)

        val rightAxis: YAxis = emgChart.getAxisRight()
        rightAxis.isEnabled = false

        btn_startTest.setOnClickListener { feedMultiple() }
        
    }

    private fun addEntry() {
        val data: LineData = emgChart.data
        if (data != null) {
            var set = data.getDataSetByIndex(0)
            // set.addEntry(...); // can be called as well
            if (set == null) {
                set = createSet()
                data.addDataSet(set)
            }
            data.addEntry(Entry(set.entryCount.toFloat(), (Math.random() * 40).toFloat() + 30f), 0)
            data.notifyDataChanged()

            // let the chart know it's data has changed
            emgChart.notifyDataSetChanged()

            // limit the number of visible entries
            emgChart.setVisibleXRangeMaximum(120f)
            // chart.setVisibleYRange(30, AxisDependency.LEFT);

            // move to the latest entry
            emgChart.moveViewToX(data.entryCount.toFloat())

            // this automatically refreshes the chart (calls invalidate())
            // chart.moveViewTo(data.getXValCount()-7, 55f,
            // AxisDependency.LEFT);
        }
    }

    private fun createSet(): LineDataSet {
        val set = LineDataSet(null, "Dynamic Data")
        set.axisDependency = AxisDependency.LEFT
        set.color = ColorTemplate.getHoloBlue()
        set.setCircleColor(Color.WHITE)
        set.lineWidth = 2f
        set.circleRadius = 4f
        set.fillAlpha = 65
        set.fillColor = ColorTemplate.getHoloBlue()
        set.highLightColor = Color.rgb(244, 117, 117)
        set.valueTextColor = Color.WHITE
        set.valueTextSize = 9f
        set.setDrawValues(false)
        return set
    }

    private var thread: Thread? = null

    private fun feedMultiple() {
        if (thread != null)
            thread!!.interrupt()

        val runnable = Runnable { addEntry() }
        thread = Thread {
            for (i in 0..999) {

                // Don't generate garbage runnable inside the loop.
                runOnUiThread(runnable)
                try {
                    Thread.sleep(25)
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
            }
        }
        thread!!.start()
    }

    private fun receiveData() {

        val buffer = ByteArray(1024)
        var bytes: Int
        val handler = Handler()
        var stopWorker = false
        Log.d(TAG, "Inside ReceiveData")

        val workerThread = Thread {
            while (!Thread.currentThread().isInterrupted && !stopWorker) {

                try {
                    bytes = m_bluetoothSocket!!.inputStream.read(buffer)
                    if (bytes > 0) {
                        val incomingMessage = String(buffer, 0, bytes)
                        Log.d(TAG, "InputStream : $incomingMessage")
                        yVal = incomingMessage.toInt()
                       
                    } else {
                        Toast.makeText(this , "bytes is less than zero" , Toast.LENGTH_SHORT).show()

                    }
                } catch (ex: IOException) {
                    stopWorker = true
                }
            }
        }
        workerThread.start()
    }

I have a sensor and by connecting it to an Arduino board I can see the sensor signals in the Serial Plotter in the form of a chart on Arduino IDE.
I want to see the same signals on my APP in real time by using a Bluetooth module.
I'm using the MPAndroidChart library to plot a chart in Kotlin.
To plot a chart using MPAndroidChart and for more information on how to use MPAndroidChart,
I checked the sample program code that published in Google Play from the following link in Github
https://github.com/PhilJay/MPAndroidChart
and converted the Java code to Kotlin, and now I can Plot chart with random numbers.
But I plan to plot my own graph using sensor data that received via Bluetooth.
I wrote a ReceiveData function and I want to get the data with this method and give it to the addEntry function so that instead of plotting a graph with random numbers, I plot my graph with Bluetooth data.
But I have no idea what to do.
By calling the feedMultiple function in

btn_startTest.setOnClickListener { feedMultiple() }

and using the following code in addEntry a graph plotting with random numbers.

data.addEntry(Entry(set.entryCount.toFloat(), (Math.random() * 40).toFloat() + 30f), 0)

But The problem I have is that , I d’not know how to transfer the data that received via Bluetooth to the addEntry function and use them instead of random numbers.
This is the complete code of my application:

class ElectromyographyAnalysis : AppCompatActivity(), OnChartValueSelectedListener {

    companion object {
        val TAG = "EMGSensor"
        val APP_NAME = "EMGSensor"

        var m_myUUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb")
        var m_bluetoothSocket: BluetoothSocket? = null
        lateinit var m_progress: ProgressDialog
        lateinit var m_bluetoothAdapter: BluetoothAdapter
        var m_isConnected: Boolean = false
        lateinit var m_address: String

        var xVal: Int = 0
        var yVal: Int = 0
    }

    lateinit var emgChart: LineChart


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.electromyography_analysis_layout)

        title = "Electromyography Analysis"

        m_address = intent.getStringExtra(SelectDeviceActivity.EXTRA_ADDRESS).toString()

        ConnectToDevice(this).execute()

        //add this new
        window.setFlags(
            WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN
        )


        emgChart = findViewById(R.id.emg_lineChart)
        emgChart.setOnChartValueSelectedListener(this)

        // enable description text
        emgChart.description.isEnabled = true

        // enable touch gestures
        emgChart.setTouchEnabled(true)

        // enable scaling and dragging

        // enable scaling and dragging
        emgChart.isDragEnabled = true
        emgChart.setScaleEnabled(true)
        emgChart.setDrawGridBackground(false)

        // if disabled, scaling can be done on x- and y-axis separately
        emgChart.setPinchZoom(true)

        // set an alternative background color
        emgChart.setBackgroundColor(Color.LTGRAY)

        val data = LineData()
        data.setValueTextColor(Color.WHITE)

        //add empty data

        // add empty data
        emgChart.data = data

        // get the legend (only possible after setting data)

        // get the legend (only possible after setting data)
        val l: Legend = emgChart.legend

        // modify the legend ...

        // modify the legend ...
        l.form = LegendForm.LINE
        //l.typeface =
        l.textColor = Color.WHITE

        val xl: XAxis = emgChart.xAxis
        //xl.typeface = tfLight
        xl.textColor = Color.WHITE
        xl.setDrawGridLines(false)
        xl.setAvoidFirstLastClipping(true)
        xl.isEnabled = true

        val leftAxis: YAxis = emgChart.getAxisLeft()
        //leftAxis.typeface = tfLight
        leftAxis.textColor = Color.WHITE
        leftAxis.axisMaximum = 100f
        leftAxis.axisMinimum = 0f
        leftAxis.setDrawGridLines(true)

        val rightAxis: YAxis = emgChart.getAxisRight()
        rightAxis.isEnabled = false

        btn_startTest.setOnClickListener { feedMultiple() }
        
    }

    private fun addEntry() {
        val data: LineData = emgChart.data
        if (data != null) {
            var set = data.getDataSetByIndex(0)
            // set.addEntry(...); // can be called as well
            if (set == null) {
                set = createSet()
                data.addDataSet(set)
            }
            data.addEntry(Entry(set.entryCount.toFloat(), (Math.random() * 40).toFloat() + 30f), 0)
            data.notifyDataChanged()

            // let the chart know it's data has changed
            emgChart.notifyDataSetChanged()

            // limit the number of visible entries
            emgChart.setVisibleXRangeMaximum(120f)
            // chart.setVisibleYRange(30, AxisDependency.LEFT);

            // move to the latest entry
            emgChart.moveViewToX(data.entryCount.toFloat())

            // this automatically refreshes the chart (calls invalidate())
            // chart.moveViewTo(data.getXValCount()-7, 55f,
            // AxisDependency.LEFT);
        }
    }

    private fun createSet(): LineDataSet {
        val set = LineDataSet(null, "Dynamic Data")
        set.axisDependency = AxisDependency.LEFT
        set.color = ColorTemplate.getHoloBlue()
        set.setCircleColor(Color.WHITE)
        set.lineWidth = 2f
        set.circleRadius = 4f
        set.fillAlpha = 65
        set.fillColor = ColorTemplate.getHoloBlue()
        set.highLightColor = Color.rgb(244, 117, 117)
        set.valueTextColor = Color.WHITE
        set.valueTextSize = 9f
        set.setDrawValues(false)
        return set
    }

    private var thread: Thread? = null

    private fun feedMultiple() {
        if (thread != null)
            thread!!.interrupt()

        val runnable = Runnable { addEntry() }
        thread = Thread {
            for (i in 0..999) {

                // Don't generate garbage runnable inside the loop.
                runOnUiThread(runnable)
                try {
                    Thread.sleep(25)
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
            }
        }
        thread!!.start()
    }

    private fun receiveData() {

        val buffer = ByteArray(1024)
        var bytes: Int
        val handler = Handler()
        var stopWorker = false
        Log.d(TAG, "Inside ReceiveData")

        val workerThread = Thread {
            while (!Thread.currentThread().isInterrupted && !stopWorker) {

                try {
                    bytes = m_bluetoothSocket!!.inputStream.read(buffer)
                    if (bytes > 0) {
                        val incomingMessage = String(buffer, 0, bytes)
                        Log.d(TAG, "InputStream : $incomingMessage")
                        yVal = incomingMessage.toInt()
                       
                    } else {
                        Toast.makeText(this , "bytes is less than zero" , Toast.LENGTH_SHORT).show()

                    }
                } catch (ex: IOException) {
                    stopWorker = true
                }
            }
        }
        workerThread.start()
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

£烟消云散 2025-01-18 07:15:32

老实说,我会使用 EventBus,因此您可以使用变量侦听来自蓝牙设备的数据。

事件总线充当侦听器。当您从蓝牙设备接收数据时,Android 会保存事件,您可以在应用程序中的任何位置使用它,也可以在 Activity 之间传递它。

但要做到这一点,您需要注册并订阅。让我们从第一个开始吧。

  1. 将 GreenRobot eventbus 添加到您的 gradle 中:

     实现 'org.greenrobot:eventbus:3.2.0' 
    
  2. 创建 EventBus(我将创建一个新的对象文件以使 eventbus 有序):

    数据类 DataEvent(var dataToSend: Float){}
    

当然,您需要注册并订阅此事件。以及如何做?

  1. 在蓝牙接收器中注册您的事件总线,您需要注册要订阅的数据(要清楚的是,您从蓝牙获取数据包的地方):

    val eventData: DataEvent = DataEvent(yourData)
    EventBus.getDefault().post(eventData)
    
  2. 订阅,当然您现在需要订阅,否则您将看不到任何数据被保存到您的事件类中。
    因为您需要绘制数据,所以在肌电图分析类中您需要订阅该事件:

    @Subscribe(threadMode = ThreadMode.MAIN)
    公共乐趣 onDataReceived(事件:DataEvent){
        if (event.dataToSend != null) {
            dataToPlot = event.dataToSend
        }
        addEntry(dataToPlot) // 这将调用您的 ADDENTRY()
    }
    

这样您将把蓝牙接收到的每一位发送到 AddEntry() 方法并绘制它们。

  1. 但是,为此您需要稍微更改一下您的 AddEntry():

     private fun addEntry(yourData:DataType) {
              验证数据:LineData = emgChart.data
    
         如果(数据!=空){
              var set = data.getDataSetByIndex(0)
             if (set1 == null) {
                 data.addDataSet(设置)
             }
    
             数据.addEntry(
                 入口(
                     set.entryCount.toFloat(),
                     数据绘图
                 ), 0
             )
             data.notifyDataChanged()
    
             // 让图表知道它的数据已经改变
             emgChart.notifyDataSetChanged()
    
             // 限制可见条目的数量
             emgChart.setVisibleXRangeMaximum(120f)
             // Chart.setVisibleYRange(30, AxisDependency.LEFT);
    
             // 移动到最新条目
             emgChart.moveViewToX(data.entryCount.toFloat())
    
             // 这会自动刷新图表(调用 invalidate())
             // Chart.moveViewTo(data.getXValCount()-7, 55f,
             // AxisDependency.LEFT);
         }
     } 
    
  2. 您将不需要 feedMultiple Thread,并且您还将获得性能更好的实时图表。

To be honest I would use EventBus, so you have a variable listening for data coming from your Bluetooth device.

The eventbus works as a listener. When you receive your data from the Bluetooth device Android save the event and you can use it wherever in your app, passing it between activities too.

But to do that you nees to register and subscribe. Let's start from the first.

  1. Add to your dependencies GreenRobot eventbus in your gradle:

       implementation 'org.greenrobot:eventbus:3.2.0' 
    
  2. Create the EventBus (I would make a new object file to make eventbus ordered):

    data class DataEvent(var dataToSend: Float){}
    

Of course, you need to register and subscribe to this events. And how to?

  1. Register your eventbus inside your bluetooth receiver you need to register the data to subscribe (where you get the data package from the bluetooth to be clear):

    val eventData: DataEvent = DataEvent(yourData)
    EventBus.getDefault().post(eventData)
    
  2. Subscribe, you need to subscribe of course now or you won't see any data being saved into your event class.
    Because you need to plot data, in your ElectromyographyAnalysis Class you need to subscribe to the event:

    @Subscribe(threadMode = ThreadMode.MAIN)
    public fun onDataReceived(event: DataEvent) {
        if (event.dataToSend != null) {
            dataToPlot = event.dataToSend
        }
        addEntry(dataToPlot) // THIS WILL CALL YOUR ADDENTRY()
    }
    

This way you will send to the AddEntry() method every single bit the bluetooth receives and plot them.

  1. But, to do so you need to change a bit your AddEntry():

        private fun addEntry(yourData:DataType) {
              val data: LineData = emgChart.data
    
         if (data != null) {
              var set = data.getDataSetByIndex(0)
             if (set1 == null) {
                 data.addDataSet(set)
             }
    
             data.addEntry(
                 Entry(
                     set.entryCount.toFloat(),
                     datatoplot
                 ), 0
             )
             data.notifyDataChanged()
    
             // let the chart know it's data has changed
             emgChart.notifyDataSetChanged()
    
             // limit the number of visible entries
             emgChart.setVisibleXRangeMaximum(120f)
             // chart.setVisibleYRange(30, AxisDependency.LEFT);
    
             // move to the latest entry
             emgChart.moveViewToX(data.entryCount.toFloat())
    
             // this automatically refreshes the chart (calls invalidate())
             // chart.moveViewTo(data.getXValCount()-7, 55f,
             // AxisDependency.LEFT);
         }
     } 
    
  2. You won't need feedMultiple Thread and you will have also a better performant real time graph.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文