NFC P2P with PN532 module on Arduino and Android Kotlin - android

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

Related

Upload recorded file to AWS S3 storage or google drive

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" />

How to read data from notification characteristic BLE with Kotlin

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.

Android 11 : How to write at 30 files per seconds on removable storage (ssd drive on usb)

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 !

Login button needs to be clicked twice to login

i'm making an app on AndroidStudio and I need to verify credentials when they log in to the app. The app works with an API and to verifiy credentials i created this function in the database to check someones email and password:
(postgresql)
create or replace function login (emailf text, passwordf text)
returns boolean
language plpgsql
as
$$
declare pp text;
begin
pp = (select pass_w from utilizador where utilizador.email = emailf);
if (pp = passwordf) then return true;
else return false;
end if; end
$$
I'm parsing the data through this CheckLoginas function:
var bola: Boolean? = null
fun CheckLoginas(c: Context?, email: String, pass: String): Boolean? {
var mQueue: RequestQueue
mQueue = Volley.newRequestQueue(c);
var url = "https://myurl.com" + "/utilizador/login/" + email + "/" + pass
val request = JsonArrayRequest(Request.Method.GET, url, null, Response.Listener {
response ->try {
var jsonArray = JSONArray()
jsonArray = response.getJSONArray(0)
for (i in 0 until jsonArray.length())
{
val jsonObject : JSONObject? = jsonArray.getJSONObject(i)
//val user = jsonArray.getJSONObject(i)
//val bool = jsonObject.getBoolean("login")
val boo : Boolean = jsonObject!!.getBoolean("login")
println("im inside CheckLoginas boo $boo\n\n")
bola = boo
}
} catch (e: JSONException) {
e.printStackTrace()
}
}, Response.ErrorListener { error -> error.printStackTrace() })
mQueue?.add(request)
return bola
}
'bola' variable is a global variable because I needed to return a boolean from the function so I can know if the credentials check (or not) in another activity.
The Problem:
To login when the credentials are correct, I have to press twice in the login button. If the email and password are correct, the first time I press it gives me the "Wrong credentials" error and in the second time it logs in. I already tried to do it with a while(), I checked it step by step and it seems fine, nothing seems to work to fix this error... The function works, the API too, and the app itself kinda works too, it just has this bug of clicking twice on the button... This is the activity code:
package com.example.crowdzero
import CheckLoginas
import Database
import android.content.Intent
import android.os.Bundle
import android.view.View.OnFocusChangeListener
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.textfield.TextInputLayout
import java.lang.Thread.sleep
class Login : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
val log_in_btn_log_in = findViewById<Button>(R.id.log_in_btn_log_in)
val log_in_btn_registar = findViewById<Button>(R.id.log_in_btn_registar)
log_in_btn_log_in.setOnClickListener {
verificacao()
}
log_in_btn_registar.setOnClickListener {
val intent = Intent(this, Registo::class.java)
startActivity(intent)
}
}
private fun verificacao() {
val log_in_input_text_email = findViewById<TextInputLayout>(R.id.log_in_input_text_email)
val log_in_input_text_password = findViewById<TextInputLayout>(R.id.log_in_input_text_password)
val string_email = log_in_input_text_email?.getEditText()?.getText().toString()?.trim()
val string_password = log_in_input_text_password?.getEditText()?.getText().toString()?.trim()
if (string_email.isNullOrEmpty())
{
log_in_input_text_email.setError(" ")
}
else if (string_password.isNullOrEmpty())
{
log_in_input_text_password.setError(" ")
}
else
{
val email = log_in_input_text_email.editText?.text.toString()
val password = log_in_input_text_password.editText?.text.toString()
//var baca = CheckLoginas(this,email,password)
println(email)
println(password)
var baca: Boolean? = null
baca = CheckLoginas(this, email, password)
//baca = CheckLoginas(this,email,password)
if (baca == false) {
//Toast.makeText(this, "Esta conta não está registada", Toast.LENGTH_SHORT).show();
println("Im inside if in login baca $baca")
} else if (baca == true) {
Toast.makeText(this, email, Toast.LENGTH_SHORT).show();
Toast.makeText(this, password, Toast.LENGTH_SHORT).show();
val intent = Intent(this, Home::class.java)
startActivity(intent)
finish()
}
}
}
}
When I test this with an actual email and password from the database, baca variable stays false when it should be true, since CheckLoginas boo var is true. This is what is causing the problem.
image that shows it
I'm fairly new to the Database-API-App thing, so please forgive me if its a trivial thing
You are calling baca = CheckLoginas(this, email, password)
baca will not update immedietly, the next line if (baca == false) will be executed before you API response arrives, so after you got some response baca becomes true. This is why you need to click twice.
SOLVED:
I pretty much inserted the CheckLoginas function inside the login.kt file. It works now! It looks like this now:
package com.example.crowdzero
import Database
import android.content.Intent
import android.os.Bundle
import android.view.View.OnFocusChangeListener
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.android.volley.Request
import com.android.volley.RequestQueue
import com.android.volley.Response
import com.android.volley.toolbox.JsonArrayRequest
import com.android.volley.toolbox.Volley
import com.google.android.material.textfield.TextInputLayout
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.lang.Thread.sleep
class Login : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
val log_in_btn_log_in = findViewById<Button>(R.id.log_in_btn_log_in)
val log_in_btn_registar = findViewById<Button>(R.id.log_in_btn_registar)
log_in_btn_log_in.setOnClickListener {
verificacao()
}
log_in_btn_registar.setOnClickListener {
val intent = Intent(this, Registo::class.java)
startActivity(intent)
}
}
private fun verificacao() {
val log_in_input_text_email = findViewById<TextInputLayout>(R.id.log_in_input_text_email)
val log_in_input_text_password = findViewById<TextInputLayout>(R.id.log_in_input_text_password)
val string_email = log_in_input_text_email?.getEditText()?.getText().toString()?.trim()
val string_password = log_in_input_text_password?.getEditText()?.getText().toString()?.trim()
if (string_email.isNullOrEmpty())
{
log_in_input_text_email.setError(" ")
}
else if (string_password.isNullOrEmpty())
{
log_in_input_text_password.setError(" ")
}
else
{
val email = log_in_input_text_email.editText?.text.toString()
val password = log_in_input_text_password.editText?.text.toString()
var mQueue: RequestQueue
mQueue = Volley.newRequestQueue(this);
var url = "https://myurl.com" + "/utilizador/login/" + email + "/" + password
val request = JsonArrayRequest(Request.Method.GET, url, null, Response.Listener {
response ->try {
var jsonArray = JSONArray()
jsonArray = response.getJSONArray(0)
for (i in 0 until jsonArray.length())
{
val jsonObject : JSONObject? = jsonArray.getJSONObject(i)
//val user = jsonArray.getJSONObject(i)
//val bool = jsonObject.getBoolean("login")
val boo : Boolean = jsonObject!!.getBoolean("login")
println("im inside CheckLoginas boo $boo\n\n")
if (boo == false) {
Toast.makeText(this, "Esta conta não está registada", Toast.LENGTH_SHORT).show();
} else if (boo == true) {
Toast.makeText(this, email, Toast.LENGTH_SHORT).show();
Toast.makeText(this, password, Toast.LENGTH_SHORT).show();
val intent = Intent(this, Home::class.java)
startActivity(intent)
finish()
}
}
} catch (e: JSONException) {
e.printStackTrace()
}
}, Response.ErrorListener { error -> error.printStackTrace() })
mQueue?.add(request)
}
}
}

RxAndroidBLE do not find devices on android 4.3

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.

Categories

Resources