I am able to connect to my BLE device and send data from my Android app, but I am not able to read the data from the BLE (I need to display this data in a graph), but when I try to retrieve the values, I get a null pointer.
Here is the code for the activity page:
package com.example.lightrdetect
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattCharacteristic.*
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.example.lightrdetect.ble.ConnectionEventListener
import com.example.lightrdetect.ble.isReadable
import com.github.mikephil.charting.charts.ScatterChart
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.components.YAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.ScatterData
import com.github.mikephil.charting.data.ScatterDataSet
import com.punchthrough.blestarterappandroid.ble.ConnectionManager
import kotlinx.android.synthetic.main.activity_home_page.image_lightr
import kotlinx.android.synthetic.main.activity_tracking_page.*
import org.jetbrains.anko.alert
import java.text.SimpleDateFormat
import java.util.*
class TrackingPageActivity : AppCompatActivity() {
private lateinit var device : BluetoothDevice
private val dateFormatter = SimpleDateFormat("MMM d, HH:mm:ss", Locale.FRANCE)
private var listeners: MutableSet<WeakReference<ConnectionEventListener>> = mutableSetOf()
private val deviceGattMap = ConcurrentHashMap<BluetoothDevice, BluetoothGatt>()
private val operationQueue = ConcurrentLinkedQueue<BleOperationType>()
private var pendingOperation: BleOperationType? = null
private val characteristic by lazy {
ConnectionManager.servicesOnDevice(device)?.flatMap { service ->
service.characteristics ?: listOf()
} ?: listOf()
}
private val characteristicProperty by lazy {
characteristic.map { characteristic->
characteristic to mutableListOf<CharacteristicProperty>().apply {
if(characteristic.isNotifiable()) add(CharacteristicProperty.Notifiable)
if (characteristic.isIndicatable()) add(CharacteristicProperty.Indicatable)
if(characteristic.isReadable()) add(CharacteristicProperty.Readable)
}.toList()
}.toMap()
}
private val characteristicAdapter: CharacteristicAdapter by lazy {
CharacteristicAdapter(characteristic){characteristicProperty ->
}
}
companion object{
//var UUID_Read_notification = UUID.fromString("D973F2E1-B19E-11E2-9E96-0800200C9A66")
var UUID_Read = "D973F2E1-B19E-11E2-9E96-0800200C9A66"
}
private var notifyingCharacteristics = mutableListOf<UUID>()
override fun onCreate(savedInstanceState: Bundle?) {
ConnectionManager.registerListener(connectionEventListener)
super.onCreate(savedInstanceState)
device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
?: error("Missing BluetoothDevice from Home Page Activity")
setContentView(R.layout.activity_tracking_page)
image_lightr.setOnClickListener {
finish()
}
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
actionBar?.hide()
supportActionBar?.hide()
ScatterChartData()
}
private fun ScatterChartData(){
readSensor(UUID_Read)
val scatterEntry = ArrayList<Entry>()
scatterEntry.add(Entry(0f, 3f))
val sensorPosition = ArrayList<Entry>()
sensorPosition.add(Entry(0f, 0f))
val scatterDataSet_sensor = ScatterDataSet(sensorPosition, "Sensor")
scatterDataSet_sensor.color = resources.getColor(R.color.white)
scatterDataSet_sensor.setScatterShape(ScatterChart.ScatterShape.CHEVRON_DOWN)
scatterDataSet_sensor.scatterShapeSize = 30f
val scatterDataSet = ScatterDataSet(scatterEntry, "Target")
scatterDataSet.color = resources.getColor(R.color.jaune_woodoo)
scatterDataSet.setScatterShape(ScatterChart.ScatterShape.CIRCLE)
scatterDataSet.valueTextColor = resources.getColor(R.color.transparent )
scatterDataSet.scatterShapeSize = 30f
val scatterlistfinal = ArrayList<ScatterDataSet>()
scatterlistfinal.add(scatterDataSet)
scatterlistfinal.add(scatterDataSet_sensor)
val scatterData = ScatterData(scatterlistfinal as List<ScatterDataSet>)
chart1.data = scatterData
chart1.setBackgroundColor(resources.getColor(R.color.transparent))
chart1.animateXY(1000, 1000)
chart1.legend.isEnabled = false
val xAxis : XAxis = chart1.xAxis
xAxis.position = XAxis.XAxisPosition.TOP
//xAxis.setDrawGridLines(true)
xAxis.axisLineColor = resources.getColor(R.color.white)
xAxis.axisMaximum = 90f
xAxis.axisMinimum = -90f
xAxis.textColor = resources.getColor(R.color.white)
xAxis.axisLineWidth = 5f
val yAxisL : YAxis = chart1.axisLeft
yAxisL.textColor = resources.getColor(R.color.white)
yAxisL.isInverted = true
yAxisL.axisMaximum = 5f
yAxisL.axisMinimum = 0f
yAxisL.axisLineWidth = 0f
yAxisL.setLabelCount(6, true)
yAxisL.axisLineColor = resources.getColor(R.color.transparent)
val yAxisR : YAxis = chart1.axisRight
yAxisR.textColor = resources.getColor(R.color.white)
yAxisR.isInverted = true
yAxisR.axisMaximum = 5f
yAxisR.axisMinimum = 0f
yAxisR.axisLineWidth = 0f
yAxisR.setLabelCount(6, true)
yAxisR.axisLineColor = resources.getColor(R.color.transparent)
}
private fun showCharacteristicOptions(characteristic: BluetoothGattCharacteristic) {
characteristicProperty[characteristic]?.let { properties ->
selector("Select an action to perform", properties.map { it.action }) { _, i ->
when (properties[i]) {
CharacteristicProperty.Readable -> {
//log("Reading from ${characteristic.uuid}")
ConnectionManager.readCharacteristic(device, characteristic)
}
CharacteristicProperty.Notifiable, CharacteristicProperty.Indicatable -> {
if (notifyingCharacteristics.contains(characteristic.uuid)) {
//log("Disabling notifications on ${characteristic.uuid}")
ConnectionManager.disableNotifications(device, characteristic)
} else {
//log("Enabling notifications on ${characteristic.uuid}")
ConnectionManager.enableNotifications(device, characteristic)
}
}
}
}
}
}
private fun readSensor(characteristic: String){
var gattCharacteristic = BluetoothGattCharacteristic(UUID.fromString(characteristic), PROPERTY_READ, PERMISSION_READ_ENCRYPTED)
showCharacteristicOptions(gattCharacteristic)
var data : String
if (gattCharacteristic !=null) {
ConnectionManager.enableNotifications(device, gattCharacteristic)
data = ConnectionManager.readCharacteristic(device, gattCharacteristic).toString()
Log.d("sensor", "value " + data)
}
}
private val connectionEventListener by lazy {
ConnectionEventListener().apply {
onDisconnect = {
runOnUiThread {
alert {
title = "Disconnected"
message = "Disconnected from device."
positiveButton("ok"){onBackPressed()}
}.show()
}
}
onCharacteristicRead = {_, characteristic ->
Log.i("Tracking page","Read from ${characteristic.uuid}: ${characteristic.value.toHexString()}")
}
onNotificationsEnabled = {_,characteristic ->
Log.i("Tracking page","Enabled notifications on ${characteristic.uuid}")
notifyingCharacteristics.add(characteristic.uuid)
}
}
}
private enum class CharacteristicProperty {
Readable,
Writable,
WritableWithoutResponse,
Notifiable,
Indicatable;
val action
get() = when (this) {
Readable -> "Read"
Writable -> "Write"
WritableWithoutResponse -> "Write Without Response"
Notifiable -> "Toggle Notifications"
Indicatable -> "Toggle Indications"
}
}
}
and there is the error that I have
2022-03-17 10:49:54.768 31034-31034/com.example.lightrdetect E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.lightrdetect, PID: 31034
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.lightrdetect/com.example.lightrdetect.TrackingPageActivity}: java.lang.NullPointerException: gattCharacteristic.getValue() must not be null
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3851)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4027)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:247)
at android.app.ActivityThread.main(ActivityThread.java:8676)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
Caused by: java.lang.NullPointerException: gattCharacteristic.getValue() must not be null
at com.example.lightrdetect.TrackingPageActivity.ScatterChartData(TrackingPageActivity.kt:89)
at com.example.lightrdetect.TrackingPageActivity.onCreate(TrackingPageActivity.kt:78)
at android.app.Activity.performCreate(Activity.java:8215)
at android.app.Activity.performCreate(Activity.java:8199)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3824)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4027)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:247)
at android.app.ActivityThread.main(ActivityThread.java:8676)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
this is a screenshot of the nRF application that shows me all the features:
I checked with the BLE module support and they told me that:
The Android application can write on the Rx characteristic and automatically the data will be sent on the UART (Tera Term or a µC connected on UART)
The µC or Tera Term to push data will have to emit on the Tx, this is what the Application code of the ST Serial Port Profile code does, when it receives on the UART an end of string character (CR+LF) (to be set in the Tera Term or in the STM32 Application code).
However, for the Android application to receive the data, it must be registered on the notifications (slide 10 of the doc - the slide refers to an Ios mobile on Android to activate the notifications, you must click on the 3 arrows)
I checked with Tera Term, I can see the data on nRF.
My question now is how can I read the characteristic notification?
Best regard.
The provided image shows the 3 services your device offers:
Generic Attribute
Generic Access
Unknown Service
The first two are pretty standard for a BLE device. The Generic Attribute Service offers you one characteristic called "Service Changed" which allows to get notified if your device changes something about its services during runtime. The Generic Access Service contains the device name and other information.
You probably want to talk to the Service labeled "Unknown Service" by nRF Connect. This simply means that the UUID used for this service is not in its list of know services. nRF Connect shows two characteristics for this service, one to receive data from which also allows receiving notifications, and one to send data. It basically looks like a UART over BLE implementation.
Based on your source code it seems like you are using the wrong UUID. Your companion object refers to the correct UUID for notifications, but not the correct one for reading:
companion object{
var UUID_Read_notification = UUID.fromString("D973F2E1-B19E-11E2-9E96-0800200C9A66")
var UUID_Read = UUID.fromString("00002A04-0000-1000-8000-00805F9B34FB")
}
The UUID to read from is the same as the notify UUID: D973F2E1-B19E-11E2-9E96-0800200C9A66. If you also want to write to the device you have to use the UUID D973F2E2-B19E-11E2-9E96-0800200C9A66.
As I said in the comments, the UUID you used before (00002A04-0000-1000-8000-00805F9B34FB) belongs to the "Peripheral Preferred Connection Parameters" characteristic of the Generic Access Service and only allows reading.
Related
I use implementation 'com.microsoft.signalr:signalr:6.0.8' for android (kotlin) and backend is .Net 6
but the emulator cannot connect to the server (localhost). I try to code a function to check hubConnection.connectionState, it is DISCONNECTED.
no error happened. Can anyone guide me to find the error, here is the code:
import com.microsoft.signalr.Action1
import com.microsoft.signalr.HubConnection
import com.microsoft.signalr.HubConnectionBuilder
import com.microsoft.signalr.HubConnectionState
import io.reactivex.rxjava3.core.Single
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class SignalRListener private constructor(){
private var hubConnection: HubConnection
private var logger: Logger
init {
logger = LoggerFactory.getLogger(HubConnection::class.java)
// define in constructor
hubConnection = HubConnectionBuilder.create("http://10.0.2.2:5291/hubs/presence")
.withAccessTokenProvider(Single.defer { Single.just("${Constanst.TOKEN}") })
.build()
hubConnection.on("UserIsOnline",
Action1 { member: Member -> println(member.DisplayName + "online") },
Member::class.java
)
hubConnection.on("UserIsOffline",
Action1 { username: String -> println(username+" offline") },
String::class.java
)
hubConnection.on(
"GetOnlineUsers",
Action1 { usersOnline : List<Member> ->
for (item in usersOnline) {
println(item.DisplayName)
}
},
List::class.java
)
hubConnection.start().doOnError({ logger.info("Client connected error.") })
}
private object Holder { val INSTANCE = SignalRListener() }
companion object {
#JvmStatic
fun getInstance(): SignalRListener{
return Holder.INSTANCE
}
}
fun stopHubConnection(){
if(hubConnection.connectionState == HubConnectionState.CONNECTED){
hubConnection.stop()
}
}
fun getConnectionState(){
println(hubConnection.connectionState.toString())
}
fun log(){
logger.info("Debug infor siganlR {}", hubConnection.connectionId)
}
}
Web (React) runs well with the backend.
class MainActivity : AppCompatActivity() {
lateinit var signalR: SignalRListener;
var btnCheck: Button? = null
var btnLog: Button? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
signalR = SignalRListener.getInstance()
btnCheck = findViewById(R.id.btnCheck)
btnCheck?.setOnClickListener {
signalR.getConnectionState()
}
btnLog = findViewById(R.id.btnLog)
btnLog?.setOnClickListener {
signalR.log()
}
}
}
As you are in the android emulator, You have to access your localhost so that it reaches your server. If you need internet through proxy you can also set it from the Settings and Proxy and there you can define your proxy settings.
I fixed the problem with the following:
in BE(.Net Core) remove this line:
app.UseHttpsRedirection();
and the client calls http not https:
hubConnection = HubConnectionBuilder.create("http://10.0.2.2:5291/hubs/presence")
hubConnection.start().blockingAwait()
It worked fine
I am trying to send a string message from Android to Arduino with the PN532 module. When I approach the smartphone (with the app open) to the module, the "tap to beam" UI shows up, but after I tap on the screen, the phone tells me to approach the two devices again. After I approach them again (with the "approach the devices again" message still displayed on the screen), nothing happens and arduino prints out "failed".
Here is the Kotlin code:
package com.cerowski.nfcclient
import android.nfc.NdefMessage
import android.nfc.NdefRecord
import android.nfc.NfcAdapter
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.viewpager.widget.ViewPager
import com.cerowski.nfcclient.databinding.ActivityMainBinding
import com.cerowski.nfcclient.ui.main.SectionsPagerAdapter
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import java.io.File
import java.io.UnsupportedEncodingException
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {config(); sendid();
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val sectionsPagerAdapter = SectionsPagerAdapter(this, supportFragmentManager)
val viewPager: ViewPager = binding.viewPager
viewPager.adapter = sectionsPagerAdapter
val tabs: TabLayout = binding.tabs
tabs.setupWithViewPager(viewPager)
val fab: FloatingActionButton = binding.fab
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
}
fun config(){
val dir = getExternalFilesDir(null);
val file = File(dir,"config");
var msgtext="";
val msg = TextView (this)
if (dir!=null) {
if (!file.exists()) {msgtext = "config not found, attempting to create..."; file.createNewFile(); if(file.exists()) {msgtext = "config created successfully"; file.writeText(idgenerator())} else {msgtext = "problem creating file"}}
else {msgtext = (file.readText())}
} else {msgtext = "app directory not found"}
}
fun idgenerator() : String {
val allowedChars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
return (1..16)
.map { allowedChars.random() }
.joinToString("")
}
fun sendid() {
val dir = getExternalFilesDir(null);
val file = File(dir,"config");
//var bytes = file.readBytes();
//var nfcmsg = NdefMessage(bytes);
//var msgtext="";
var nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter != null) {
val msg: String = file.readText().toString()
val languageCode: ByteArray
val msgBytes: ByteArray
try {
languageCode = "en".toByteArray(charset("US-ASCII"))
msgBytes = msg.toByteArray(charset("UTF-8"))
} catch (e: UnsupportedEncodingException) {
return
}
val messagePayload = ByteArray(
1 + languageCode.size
+ msgBytes.size
)
messagePayload[0] = 0x02.toByte() // status byte: UTF-8 encoding and
// length of language code is 2
// length of language code is 2
System.arraycopy(
languageCode, 0, messagePayload, 1,
languageCode.size
)
System.arraycopy(
msgBytes, 0, messagePayload, 1 + languageCode.size,
msgBytes.size
)
val message: NdefMessage
val records = arrayOfNulls<NdefRecord>(1)
val textRecord = NdefRecord(
NdefRecord.TNF_WELL_KNOWN,
NdefRecord.RTD_TEXT, byteArrayOf(), messagePayload
)
records[0] = textRecord
message = NdefMessage(records)
nfcAdapter.setNdefPushMessage(message, this);
}
else {Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show();}
}
}
And here is the code for my Arduino Uno with the PN532 module (the red one, if you search for photos online):
// Receive a NDEF message from a Peer
// Requires SPI. Tested with Seeed Studio NFC Shield v2
#include "SPI.h"
#include "PN532_SPI.h"
#include "snep.h"
#include "NdefMessage.h"
PN532_SPI pn532spi(SPI, 10);
SNEP nfc(pn532spi);
uint8_t ndefBuf[128];
void setup() {
Serial.begin(9600);
Serial.println("NFC Peer to Peer Example - Receive Message");
}
void loop() {
Serial.println("Waiting for message from Peer");
int msgSize = nfc.read(ndefBuf, sizeof(ndefBuf));
if (msgSize > 0) {
NdefMessage msg = NdefMessage(ndefBuf, msgSize);
msg.print();
Serial.println("\nSuccess");
} else {
Serial.println("Failed");
}
delay(3000);
}
The little switches on the NFC module are in the right positions and the module is connected to the board as it should be (for SPI), So I do not know what is the thing that causes it to fail.
Any help is much appreciated.
First check if it's even a) available, b) enabled and c) register & implement the callbacks. In Java:
MainActivity extends AppCompatActivity implements
NfcAdapter.CreateNdefMessageCallback,
NfcAdapter.OnNdefPushCompleteCallback,
NfcAdapter.CreateBeamUrisCallback {
...
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
) {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (!nfcAdapter.isEnabled()) {
startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
} else if (!nfcAdapter.isNdefPushEnabled()) {
startActivity(new Intent(Settings.ACTION_NFCSHARING_SETTINGS));
} else {
nfcAdapter.setNdefPushMessageCallback(this);
nfcAdapter.setOnNdefPushCompleteCallback(this);
nfcAdapter.setNdefPushMessage(nfcMessage, this);
}
}
Deprecated in Build.VERSION_CODES.Q means still available; maybe <=.
These callbacks might also provide you with more detail, why it even fails.
Else you'll send a NDEF message and it will not know what to do next ...
Alike this you might also be able to produce a proper error message.
Also see: https://developer.android.com/training/beam-files/send-files
I'm trying to create a BLE service that will scan for devices and using rxKotlin create an observable that will allow another class to observe when a device is found. I'm confused on how to create the observable that will allow another class to subscribe and tutorials are all over the place. Can someone give me a pointer on how to do so or a good tutorial.
Bluetoothservice class callback where devices are discovered
var foundDeviceObservable: Observable<BluetoothDevice> = Observable.create { }
private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
with(result.device) {
var foundName = if (name == null) "N/A" else name
foundDevice = BluetoothDevice(
foundName,
address,
address,
result.device.type.toString()
)
foundDeviceObservable.subscribe {
//Update Observable value?
}
}
}
}
class DeviceListViewModel(application: Application) : AndroidViewModel(application) {
private val bluetoothService = BLEService()
//Where I am trying to do logic with device
fun getDeviceObservable(){
bluetoothService.getDeviceObservable().subscribe{ it ->
}
}
Solution
Was able to find the solution after reading user4097210's reply. Just had to change the found device to
var foundDeviceObservable: BehaviorSubject<BluetoothDevice> = BehaviorSubject.create()
and then call the next method in the callback
private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
with(result.device) {
var foundName = if (name == null) "N/A" else name
foundDevice = BluetoothDevice(
foundName,
address,
address,
result.device.type.toString()
)
foundDeviceObservable.onNext(foundDevice)
}
}
}
use BehaviorSubject
// create a BehaviorSubject
var foundDeviceObservable: BehaviorSubject<BluetoothDevice> = BehaviorSubject()
// call onNext() to send new found device
foundDeviceObservable.onNext(foundDevice)
// do your logic use foundDeviceObservable
foundDeviceObservable.subscribe(...)
I have recently started working on Android, Kotlin and MPAndroidChart. I am developing an Android application that receives data from a bluetooth server and the data obtained should be plotted in real time using MPAndroidChart.
Here is the Kotlin code:
package com.example.flowsensor
import ...
class ConnectionActivity:AppCompatActivity() {
companion object{
val TAG = "FlowSensor"
val APP_NAME = "FlowSensor"
var myUUID: UUID = UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66")
var mBluetoothSocket: BluetoothSocket? = null
lateinit var mProgress: ProgressDialog
lateinit var mBluetoothAdapter: BluetoothAdapter
var mIsConnected: Boolean = false
lateinit var mAddress: String
lateinit var editText:EditText
lateinit var mChart:LineChart
var xVal:Int = 0
var yVal:Int = 0
}
override fun onCreate(savedInstanceState: Bundle?) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_connection)
mChart = findViewById<LineChart>(R.id.line_chart)
receive_button.setOnClickListener{
ReceiveData()
}
}
//1 parameters missing
private fun mAddEntry() {
Log.d(TAG, "yVal : $yVal")
var mData = mChart.data
if(mData != null){
var mDataSet = mData.getDataSetByIndex(0)
if(mDataSet == null){
mDataSet = createDataSet()
mData.addDataSet(mDataSet)
}
var mEntry:Entry = Entry( xVal.toFloat(), yVal.toFloat())
xVal++
mData.addEntry(mEntry, 0)
//Notify chart data has changed
mChart.notifyDataSetChanged()
//Limit no of visible entries
// mChart.setVisibleXRange(1f, 6f)
mChart.setVisibleXRangeMaximum(6f)
//Scroll to the last entry
mChart.moveViewToX(xVal.toFloat())
}
}
//1 parameter missing
private fun createDataSet(): LineDataSet? {
var mDataSet = LineDataSet(null, "Data vals")
//mDataSet.setDrawCubic
mDataSet.cubicIntensity = 0.2f
mDataSet.axisDependency = YAxis.AxisDependency.LEFT
mDataSet.setColor(ColorTemplate.getHoloBlue())
mDataSet.setCircleColor(ColorTemplate.getHoloBlue())
mDataSet.lineWidth = 2f
mDataSet.circleSize = 4f
mDataSet.fillAlpha = 65
mDataSet.fillColor = ColorTemplate.getHoloBlue()
mDataSet.highLightColor = Color.rgb(244, 117, 177)
mDataSet.valueTextColor = Color.WHITE
mDataSet.valueTextSize = 10f
return mDataSet
}
private fun ReceiveData() {
val buffer = ByteArray(1024) // buffer store for the stream
var bytes: Int // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
Log.d(TAG, "Inside ReceiveData()")
while (true) { // Read from the InputStream
if(mBluetoothSocket != null)
{
try {
bytes = mBluetoothSocket!!.inputStream.read(buffer)
val incomingMessage = String(buffer, 0, bytes)
Log.d(TAG, "InputStream: $incomingMessage")
yVal = incomingMessage.toInt()
mAddEntry()
} catch (e: IOException) {
Log.e(TAG, "write: Error reading Input Stream. " + e.message)
break
}
}
}
}
}
Here is the logcat log logged in verbose mode.
App starts in the Main Activity(code not attached here), looks for paired devices then using
val intent = Intent(this, ConnectionActivity::class.java)
intent.putExtra(EXTRA_ADDRESS, address)
startActivity(intent) code, control reaches Connection Activity (code attached above).
App is successfully receiving data from server; verified by observing logcat. In ReceiveData(), I am trying to pass the received data to mAddEntry() which is responsible for drawing the graph. But the problem is, data is plotted only after I terminate the socket, so after Line no 112 in the attached logcat log, all the data is plotted at once (no data loss). I want to plot data in real time, and this is the problem I am facing.
Note: graph plotting has been independently verified by passing dummy data in real-time inside onCreate() in ConnectionActivity class using the timer.scheduleAtFixedRate. This is working as expected.
by calling the reciveData() inside the receive_button onClickListner you are executing while loop that will run until Bluetooth socket is connected and it is runing on the MainThread (UIThread) and it will block the UI thread and doesn't let any other ui updateto happen you should execute the reciveData() function on the background and when you want the plot the data in chart you should pass the data in ui thread or call the mAddEntry() function in ui thread
Hello I'm using RxAndroidBLE to detect a BLE device. On android 6 >= everything seems to work okay but not on a 4.3 device.
My app can only discover the desirable BLE device only once at start. After the device has been discovered no more new discoveries at all until I restart the app. Any advice would be highly appreciated.
Below minimum (not)working code example:
MainActivity
import android.content.Context
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.util.Log
import com.polidea.rxandroidble.RxBleClient
import com.polidea.rxandroidble.exceptions.BleScanException
import com.polidea.rxandroidble.scan.ScanResult
import com.polidea.rxandroidble.scan.ScanSettings
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startLeScan(applicationContext)
}
private var rxBleClient: RxBleClient? = null
private var scanSubscription: Subscription? = null
private var handler: Handler? = null
private var timer: Timer? = null
private var timerTask: TimerTask? = null
private var delay: Int = 0
private fun isScanning(): Boolean {
return scanSubscription != null
}
fun startLeScan(context: Context) {
rxBleClient = MyaPP.getRxBleClient(context)
if (isScanning()) {
scanSubscription?.unsubscribe()
} else {
scanSubscription = rxBleClient?.scanBleDevices(
com.polidea.rxandroidble.scan.ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build())
?.observeOn(AndroidSchedulers.mainThread())
//?.doOnNext(this::newDevicesFound)
?.doOnUnsubscribe(this::clearSubscription)
?.subscribe(this::newDevicesFound, this::onScanFailure)
}
if(handler == null) {
handler = Handler()
timer = Timer(false)
timerTask = object : TimerTask() {
override fun run() {
handler?.post {
if (delay > 7) {
delay = 0
val service = Executors.newSingleThreadExecutor()
service.submit(Runnable {
//startLeScan(context)
})
} else {
delay = delay + 1
}
}
}
}
timer?.scheduleAtFixedRate(timerTask, 0, 300)
}
}
private fun newDevicesFound(devices: ScanResult) {
Log.d("WHYY??", devices.bleDevice.name)
}
fun stopScan() {
scanSubscription?.unsubscribe()
destroy()
}
private fun clearSubscription() {
scanSubscription = null
}
private fun onScanFailure(throwable: Throwable) {
if (throwable is BleScanException) {
handleBleScanException(throwable)
}
}
private fun handleBleScanException(bleScanException: BleScanException) {
val text: String
when (bleScanException.reason) {
BleScanException.BLUETOOTH_NOT_AVAILABLE -> text = "Bluetooth is not available"
BleScanException.BLUETOOTH_DISABLED -> text = "Enable bluetooth and try again"
BleScanException.LOCATION_PERMISSION_MISSING -> text = "On Android 6.0 location permission is required. Implement Runtime Permissions"
BleScanException.LOCATION_SERVICES_DISABLED -> text = "Location services needs to be enabled on Android 6.0"
BleScanException.SCAN_FAILED_ALREADY_STARTED -> text = "Scan with the same filters is already started"
BleScanException.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> text = "Failed to register application for bluetooth scan"
BleScanException.SCAN_FAILED_FEATURE_UNSUPPORTED -> text = "Scan with specified parameters is not supported"
BleScanException.SCAN_FAILED_INTERNAL_ERROR -> text = "Scan failed due to internal error"
BleScanException.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES -> text = "Scan cannot start due to limited hardware resources"
BleScanException.UNDOCUMENTED_SCAN_THROTTLE -> text = String.format(
Locale.getDefault(),
"Android 7+ does not allow more scans. Try in %d seconds",
secondsTill(bleScanException.retryDateSuggestion)
)
BleScanException.UNKNOWN_ERROR_CODE, BleScanException.BLUETOOTH_CANNOT_START -> text = "Unable to start scanning"
else -> text = "Unable to start scanning"
}
Log.w("EXCEPTION", text, bleScanException)
}
private fun secondsTill(retryDateSuggestion: Date?): Long {
if (retryDateSuggestion != null) {
return TimeUnit.MILLISECONDS.toSeconds(retryDateSuggestion.time - System.currentTimeMillis())
}
return 0
}
private fun destroy() {
timer?.cancel()
handler?.removeCallbacks(timerTask)
handler = null
timerTask = null
timer = null
}
}
MyaPP
import android.app.Application
import android.content.Context
import com.polidea.rxandroidble.RxBleClient
import com.polidea.rxandroidble.internal.RxBleLog
class MyaPP: Application() {
private var rxBleClient: RxBleClient? = null
companion object {
fun getRxBleClient(context: Context): RxBleClient? {
val application = context.applicationContext as MyaPP
return application.rxBleClient
}
}
override fun onCreate() {
super.onCreate()
rxBleClient = RxBleClient.create(this)
RxBleClient.setLogLevel(RxBleLog.DEBUG)
}
}
build.gradle
compile "com.polidea.rxandroidble:rxandroidble:1.5.0"
implementation 'io.reactivex:rxandroid:1.2.1'
manifest
<application
android:name=".MyaPP"
Your code looks a lot like the library's sample app (version 1.5.0, branch master-rxjava1). I have checked that recently on Android 4.4.4 which is the oldest I have and it worked fine. There were no API changes between 4.3 and 4.4.
What you may be experiencing is a behaviour specific to your device (feel free to share your phone model) in which it only callbacks for the first time it scans a particular peripheral. There are some threads about this topic already like this one.