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.
Related
I have created an app to record the audio in the android wear but I am not able to transfer that recorded file to mobile phone as there is no dedicated file manager in Samsung galaxy watch 4. So for that I need to upload the the recorded file to the AWS S3 STORAGE.
Here is the code for audio recording.
package com.example.watch
import android.Manifest.permission
import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Intent
import android.content.pm.PackageManager
import android.media.MediaRecorder
import android.net.Uri
import android.os.*
import android.provider.MediaStore
import android.util.Log
import android.view.WindowManager
import android.widget.Button
import android.widget.Chronometer
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import com.amplifyframework.core.Amplify
import com.amplifyframework.storage.StorageException
import com.amplifyframework.storage.result.StorageUploadFileResult
import com.example.watch.databinding.ActivityMicrophoneBinding
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.text.SimpleDateFormat
import java.util.*
#Suppress("DEPRECATION")
class Microphone : AppCompatActivity() {
// Initializing all variables..
private var Stop: Button? = null
private var Start: Button? = null
private var statusTV:TextView? = null
private var Chronometer:TextView?=null
var fileName: String? = null
var audioRecorder: MediaRecorder? = null
var audiouri: Uri? = null
var file: ParcelFileDescriptor? = null
lateinit var status:TextView
private var binding: ActivityMicrophoneBinding? = null
private lateinit var chronometer: Chronometer
private var audioFile: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_microphone)
if (!CheckPermissions())
RequestPermissions()
AmplifyInit().intializeAmplify(this#Microphone)
this.window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
)
supportActionBar!!.hide()
// initialize all variables with their layout items.
Start=binding!!.accelerometerStartButton
Stop=binding!!.accelerometerStopButton
status=binding!!.Status
chronometer=binding!!.chronometer
Start!!.setOnClickListener { // start recording method will
// start the recording of audio.
startRecording()
Start!!.isEnabled=false
status.text="Recording..."
chronometer.base = SystemClock.elapsedRealtime()
chronometer.start()
startService()
}
Stop!!.setOnClickListener { // pause Recording method will
// pause the recording of audio.
pauseRecording()
Start!!.isEnabled=true
status.text="Recording Stopped"
chronometer.stop()
uploadFile()
stopService()
}
}
private fun startRecording() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
val values = ContentValues(4)
values.put(MediaStore.Audio.Media.TITLE, fileName)
values.put(
MediaStore.Audio.Media.DATE_ADDED,
SimpleDateFormat("yyyyMMddHHmmss").format(Date())
)
values.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/Recordings/")
audiouri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values)
file = contentResolver.openFileDescriptor(audiouri!!, "w")
if (file != null) {
audioRecorder = MediaRecorder()
audioRecorder!!.setAudioSource(MediaRecorder.AudioSource.MIC)
audioRecorder!!.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
audioRecorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
audioRecorder!!.setOutputFile(file!!.fileDescriptor)
audioRecorder!!.setAudioChannels(1)
audioRecorder!!.prepare()
audioRecorder!!.start()
audioRecorder!!.setOutputFile(getAudioFile().absolutePath)
}
}
else {
RequestPermissions()
}
}
private fun getAudioFile(): File {
if (audioFile == null) {
audioFile = File(getExternalFilesDir(null), "Music/Recordings/")
}
return audioFile!!
}
#SuppressLint("MissingSuperCall")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray,
) {
// this method is called when user will
// grant the permission for audio recording.
when (requestCode) {
REQUEST_AUDIO_PERMISSION_CODE -> {
if (grantResults.isNotEmpty()) {
val permissionToRecord = grantResults[0] == PackageManager.PERMISSION_GRANTED
val permissionToStore = grantResults[1] == PackageManager.PERMISSION_GRANTED
if (permissionToRecord && permissionToStore) {
Toast.makeText(applicationContext, "Permission Granted", Toast.LENGTH_LONG)
.show()
} else {
Toast.makeText(applicationContext, "Permission Denied", Toast.LENGTH_LONG)
.show()
}
}
}
}
}
private fun CheckPermissions(): Boolean {
// this method is used to check permission
val result = ContextCompat.checkSelfPermission(applicationContext, permission.WRITE_EXTERNAL_STORAGE)
val result1 = ContextCompat.checkSelfPermission(applicationContext, permission.RECORD_AUDIO)
return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED
}
private fun RequestPermissions() {
// this method is used to request the
// permission for audio recording and storage.
ActivityCompat.requestPermissions(this,
arrayOf(permission.RECORD_AUDIO, permission.WRITE_EXTERNAL_STORAGE),
REQUEST_AUDIO_PERMISSION_CODE
)
}
private fun pauseRecording() {
Start!!.isEnabled=true
stopService(Intent(this, ForegroundService::class.java))
// below method will stop
// the audio recording.
try {
audioRecorder!!.stop()
} catch (stopException: RuntimeException) {
// handle cleanup here
}
}
fun startService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
serviceIntent.putExtra("inputExtra", "Foreground Service Example in Android")
ContextCompat.startForegroundService(this, serviceIntent)
}
fun stopService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
stopService(serviceIntent)
}
companion object {
// string variable is created for storing a file name
private var mFileName: String? = null
var permissionAccepted = false
var permissions = arrayOf(android.Manifest.permission.RECORD_AUDIO,
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
// constant for storing audio permission
const val REQUEST_AUDIO_PERMISSION_CODE = 1000
}
private fun uploadFile() {
Amplify.Storage.uploadFile(
"Audio/audio.mp3",
getAudioFile(),
{ result: StorageUploadFileResult ->
Log.i(
"MyAmplifyApp",
"Successfully uploaded: " + result.key
)
Toast.makeText(this, "File has Successfully Uploaded:" , Toast.LENGTH_SHORT).show()
}
) { storageFailure: StorageException? ->
Log.e(
"MyAmplifyApp",
"Upload failed",
storageFailure
)
Toast.makeText(this, "Upload failed", Toast.LENGTH_SHORT).show()
}
}
}
Now how can i modify my uploadfile function to upload the fiole to AWS S3, As i am getting this error.
E/amplify:aws-s3-storage:SinglePartUploadWorker: SinglePartUploadWorker failed with exception: kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=JobImpl{Cancelled}#9b17179
I/WM-WorkerWrapper: Worker result RETRY for Work [ id=48cd3651-35e2-456e-9329-81930f5277a5, tags={ UPLOAD, awsS3StoragePlugin, 9, com.amplifyframework.storage.s3.transfer.worker.RouterWorker } ]
I am using dependecies:
implementation 'com.amplifyframework:aws-api:2.1.0'
implementation 'com.amplifyframework:aws-datastore:2.1.0'
implementation 'com.amplifyframework:aws-storage-s3:2.1.0'
implementation 'com.amplifyframework:aws-auth-cognito:2.1.0'
Library:
<uses-library
android:name="com.google.android.wearable"
android:required="true" />
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 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.
I want to save images taken from my app directly to a ssd drive (removable storage) plugged in my device.
The issue I have now, is that with Android 11, I didn't manage to get the path of this storage, and so I can't write the files...
I tried use Storage Access Framework to ask the user to specify the path directly for each images but I can't use this solution as I need to write 30 images per seconds and it kept asking the user select an action on the screen.
This application is only for internal use, so I can grant all the permission without any Google deployment politics issues.
Can anybody help me, i'm so desperate...
So here's my code, I can write on a folder the user choose with SAF. Still have speed issue using DocumentFile.createFile function.
package com.example.ssdwriter
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.*
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
class MainActivity : AppCompatActivity() {
private val TAG = "SSDActivity"
private val CONTENT = ByteArray(2 * 1024 * 1024)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
grantDirectoryAccess()
}
private fun grantDirectoryAccess() {
val treeUri = contentResolver.persistedUriPermissions
if (treeUri.size > 0) {
Log.e(TAG, treeUri.size.toString())
startWriting(treeUri[0].uri)
} else {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
var resultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
result.data?.data?.let {
contentResolver.takePersistableUriPermission(
it,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
}
startWriting(result.data?.data!!)
}
}
resultLauncher.launch(intent)
}
}
private fun startWriting(uri: Uri) {
var handlerThread = HandlerThread("writer")
handlerThread.start()
var counter = 0
val handler = Handler(handlerThread.looper)
val runnableCode: Runnable = object : Runnable {
override fun run() {
Log.e(TAG, "Writing File $counter")
createFile(uri, counter++)
Log.e(TAG, "File $counter written ")
if(counter <= 150){
handler.postDelayed(this, 33)
}
}
}
handler.post(runnableCode)
}
private fun createFile(treeUri: Uri, counter: Int) {
val dir = DocumentFile.fromTreeUri(this, treeUri)
val file = dir!!.createFile("*/bmp", "Test$counter.bmp")
if (file != null) {
var outputStream = contentResolver.openOutputStream(file.uri)
if (outputStream != null) {
outputStream.write(CONTENT)
outputStream.flush()
outputStream.close()
}
}
}
}
If anyone got some clues to make this faster, it would be great !
I am using altbeacon library for contact tracing. But i am not able to find/trace my device when it comes near my other scanning phone. I have two mobiles basically, one i am using for scanning and other as beacon transmitter. I am able to transmit as beacon from my phone number 2. I tested also in Locate Beacon app. It showed my phone in that. But when i am testing on my phone number 1, its not working. I am not able to see any beacon on any logs even though my didDetermineStateForRegion and onBeaconServiceConnect is being called.
Here is my application class below:
package com.example.mybeaconprojectaye
import android.app.*
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseSettings
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.RemoteException
import android.util.Log
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.altbeacon.beacon.*
import org.altbeacon.beacon.startup.BootstrapNotifier
import org.altbeacon.beacon.startup.RegionBootstrap
import java.util.*
class MyApplication : Application(), BootstrapNotifier, BeaconConsumer {
val CHANNEL_ID = "myproximityservice"
val CHANNEL_NAME = "My Proximity Service Channel"
val backgroundBetweenScanPeriod = 6200L
val backgroundScanPeriod = 3000L
val TAG: String = "xoxo"
val REGIONID = "rangeid"
val uuidString: String= "id1"
lateinit var beaconManager: BeaconManager
private var regionBootstrap: RegionBootstrap? = null
override fun onCreate() {
super.onCreate()
beaconManager = BeaconManager.getInstanceForApplication(this)
setupBeaconScanning()
beaconManager.bind(this)
}
override fun onBeaconServiceConnect() {
Log.e(TAG, "Service connected ")
val rangeNotifier = RangeNotifier { beacons, region ->
if (beacons.size > 0) {
Log.e(TAG, "found new beacons " + beacons.size)
for (beacon: Beacon in beacons){
Log.e(TAG,"New Beacon before condition check=${beacon.id2}-${beacon.id3}-${beacon.id1}")
GlobalScope.launch {
try {
val deviceUUID: String = beacon.id1.toString()
Log.e(TAG, " before condition check=${deviceUUID}")
Log.e(
TAG,
"New Beacon=${beacon.id2}/${beacon.id3}/${beacon.id1}"
)
Log.e("xoxo","${beacon.id2}/${beacon.id3}/${beacon.id1} + "+ beacon.distance.toLong())
if (beacon.distance.toInt() < 2) {
/* val intentNotification = Intent(this#BeaconApp, HomeActivity::class.java)
intentNotification.putExtra(Constants.DeviceConstants.IS_VIBRATOR, true)
intentNotification.flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intentNotification)*/
//sendSafetyNotification()
}
}catch (ex: Exception){
Log.e(TAG, " EXCEPTION: "+ex.toString())
}
}
}
// sendBroadcast(Intent(NEW_DEVICE_ACTION))
}
}
try {
beaconManager.startRangingBeaconsInRegion(
Region(
REGIONID,
null,
null,
null
)
)
beaconManager.addRangeNotifier(rangeNotifier)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun didDetermineStateForRegion(state: Int, p1: Region?) {
Log.e("xoxo", "didDetermineStateForRegion state: "+state )
}
override fun didEnterRegion(p0: Region?) {
Log.e("xoxo", "i just saw a beacon")
}
override fun didExitRegion(p0: Region?) {
}
fun setupBeaconScanning() {
beaconManager.beaconParsers.clear()
val altbeaconParser =
BeaconParser().setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25")
altbeaconParser.setHardwareAssistManufacturerCodes(intArrayOf(0x0118))
beaconManager.beaconParsers
.add(altbeaconParser)
val iBeaconParser =
BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24")
iBeaconParser.setHardwareAssistManufacturerCodes(intArrayOf(0x004c))
beaconManager.beaconParsers
.add(iBeaconParser)
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout(BeaconParser.URI_BEACON_LAYOUT))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout(BeaconParser.EDDYSTONE_TLM_LAYOUT))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout(BeaconParser.EDDYSTONE_UID_LAYOUT))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout(BeaconParser.EDDYSTONE_URL_LAYOUT))
/* beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("s:0-1=feaa,m:2-2=00,p:3-3:-41,i:4-13,i:14-19"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("x,s:0-1=feaa,m:2-2=20,d:3-3,d:4-5,d:6-7,d:8-11,d:12-15"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("s:0-1=feaa,m:2-2=10,p:3-3:-41,i:4-20v"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("m:0-3=4c000215,i:4-19,i:20-21,i:22-23,p:24-24"))
*/
BeaconManager.setDebug(false)
val builder = Notification.Builder(this)
builder.setSmallIcon(R.drawable.ic_launcher_background)
builder.setContentTitle("Proximity Service Running")
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
)
builder.setContentIntent(pendingIntent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT
)
channel.description = "Used for scanning near by device"
val notificationManager = getSystemService(
Context.NOTIFICATION_SERVICE
) as NotificationManager
notificationManager.createNotificationChannel(channel)
builder.setChannelId(channel.id)
}
beaconManager.enableForegroundServiceScanning(builder.build(), 456)
// For the above foreground scanning service to be useful, you need to disable
// JobScheduler-based scans (used on Android 8+) and set a fast background scan
// cycle that would otherwise be disallowed by the operating system.
beaconManager.setEnableScheduledScanJobs(false)
beaconManager.backgroundBetweenScanPeriod = backgroundBetweenScanPeriod
beaconManager.backgroundScanPeriod = backgroundScanPeriod
Log.d(TAG, "setting up background monitoring for beacons and power saving")
// wake up the app when a beacon is seen
// wake up the app when a beacon is seentitle getting
val region = Region(
REGIONID,
null, null, null
)
regionBootstrap = RegionBootstrap(this, region)
}
fun startAdvertising(listener: AdvertiseListener):Boolean {
val result = BeaconTransmitter.checkTransmissionSupported(this)
Log.e("xoxo", "BLE TRANSMITTER STATUS " +(result== BeaconTransmitter.SUPPORTED).toString())
if (BeaconTransmitter.SUPPORTED != result)
return false
val beacon = Beacon.Builder()
.setId1("2f234454-cf6d-4a0f-adf2-f4911ba9ffa6")
.setId2("1")
.setId3("2")
.setManufacturer(0x0118) // Radius Networks. Change this for other beacon layouts
.setTxPower(-59)
.setDataFields(Arrays.asList(*arrayOf(0L))) // Remove this for beacon layouts without d: fields
.build()
// Change the layout below for other beacon types
val beaconParser = BeaconParser()
.setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25")
val beaconTransmitter =
BeaconTransmitter(applicationContext, beaconParser)
beaconTransmitter.startAdvertising(beacon, object : AdvertiseCallback() {
override fun onStartFailure(errorCode: Int) {
Log.e(TAG, "Advertisement start failed with code: $errorCode")
listener.onAdvertiseStatus(false)
}
override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
Log.e(TAG, "Advertisement start succeeded. uuid"+uuidString)
listener.onAdvertiseStatus(true)
}
})
return true
}
interface AdvertiseListener{
fun onAdvertiseStatus(success:Boolean)
}
}
and my MainActivity:
package com.example.mybeaconprojectaye
import android.Manifest
import android.app.AlertDialog
import android.content.DialogInterface
import android.content.pm.PackageManager
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import kotlinx.android.synthetic.main.activity_main.*
import org.altbeacon.beacon.BeaconTransmitter
class MainActivity : AppCompatActivity() {
companion object {
private const val PERMISSION_REQUEST_FINE_LOCATION = 1
private const val PERMISSION_REQUEST_BACKGROUND_LOCATION = 2
}
val TAG = "xoxo"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var result : Int = BeaconTransmitter.checkTransmissionSupported(this#MainActivity)
Log.e("xoxo", "result: "+result)
btn.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
requestPerms()
}
})
}
fun requestPerms() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
!= PackageManager.PERMISSION_GRANTED
) {
val builder =
AlertDialog.Builder(this)
builder.setTitle("Location is off")
builder.setMessage("Please allow location permission.")
builder.setPositiveButton(android.R.string.ok, null)
builder.setOnDismissListener {
requestPermissions(
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
PERMISSION_REQUEST_BACKGROUND_LOCATION
)
}
builder.show()
} else startAdvertiseBeacons()
} else startAdvertiseBeacons()
} else {
requestPermissions(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
),
PERMISSION_REQUEST_FINE_LOCATION
)
}
} else startAdvertiseBeacons()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
when (requestCode) {
PERMISSION_REQUEST_FINE_LOCATION -> {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "fine location permission granted")
requestPerms()
} else {
val builder =
AlertDialog.Builder(this)
builder.setTitle("Functionality limited")
builder.setMessage("Since location access has not been granted, this app will not be able to discover devices.")
builder.setPositiveButton(android.R.string.ok, null)
builder.setOnDismissListener { }
builder.show()
}
return
}
PERMISSION_REQUEST_BACKGROUND_LOCATION -> {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "background location permission granted")
requestPerms()
} else {
val builder =
AlertDialog.Builder(this)
builder.setTitle("Functionality limited")
builder.setMessage("Since background location access has not been granted, this app will not be able to discover devices when in the background.")
builder.setPositiveButton(
android.R.string.ok,
DialogInterface.OnClickListener { dialog, which ->
dialog.cancel()
requestPerms()
})
builder.setOnDismissListener {
requestPerms()
}
builder.show()
}
return
}
}
}
private fun startAdvertiseBeacons() {
(application as MyApplication).startAdvertising(object : MyApplication.AdvertiseListener {
override fun onAdvertiseStatus(success: Boolean) {
}
})
}
}
And here are all the logs that are being printed:
2020-06-07 05:20:25.566 23822-23822/? E/libc: Access denied finding property "persist.vendor.sys.activitylog"
2020-06-07 05:20:26.468 23822-23822/com.example.mybeaconprojectaye E/xoxo: result: 0
2020-06-07 05:20:26.810 23822-23822/com.example.mybeaconprojectaye E/xoxo: Service connected
2020-06-07 05:20:26.848 23822-23822/com.example.mybeaconprojectaye E/xoxo: didDetermineStateForRegion state: 0
Can anyone please tell me what i am doing wrong or if some step is missing. I tried looking into other answers on stackoverflow, but this is the only library where i am seeing new classes and interfaces everywhere in all answers. Too much confusion.
P.S.
I am trying to make contact tracing app. Any other library or something you can suggest will also be appreciated.
Try reversing phones 1 and 2 for your tests. If you use BeaconScope to scan, can it see the transmissions of both phones 1&2? If you use BeaconScope to transmit, can your app on either phone 1 or 2 see the beacon tranamission?
If you cannot detect beacon scope on one or both phones, check app permissions to confirm that location permission has been granted to your app. Go to Settings -> Applications -> Your App and check the granted permissions.
Also check that location is enabled globally on the phone and that Bluetooth is on.