如何在 kotlin 上的 MPAndroidChart 中绘制从蓝牙模块接收到的实时传感器值?
我有一个传感器,通过将其连接到 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
老实说,我会使用 EventBus,因此您可以使用变量侦听来自蓝牙设备的数据。
事件总线充当侦听器。当您从蓝牙设备接收数据时,Android 会保存事件,您可以在应用程序中的任何位置使用它,也可以在 Activity 之间传递它。
但要做到这一点,您需要注册并订阅。让我们从第一个开始吧。
将 GreenRobot eventbus 添加到您的 gradle 中:
创建 EventBus(我将创建一个新的对象文件以使 eventbus 有序):
当然,您需要注册并订阅此事件。以及如何做?
在蓝牙接收器中注册您的事件总线,您需要注册要订阅的数据(要清楚的是,您从蓝牙获取数据包的地方):
订阅,当然您现在需要订阅,否则您将看不到任何数据被保存到您的事件类中。
因为您需要绘制数据,所以在肌电图分析类中您需要订阅该事件:
这样您将把蓝牙接收到的每一位发送到 AddEntry() 方法并绘制它们。
但是,为此您需要稍微更改一下您的 AddEntry():
您将不需要 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.
Add to your dependencies GreenRobot eventbus in your gradle:
Create the EventBus (I would make a new object file to make eventbus ordered):
Of course, you need to register and subscribe to this events. And how to?
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):
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:
This way you will send to the AddEntry() method every single bit the bluetooth receives and plot them.
But, to do so you need to change a bit your AddEntry():
You won't need feedMultiple Thread and you will have also a better performant real time graph.