Android Question BLE manager.ReadData2( Service,Characteristic ) doesn't read new data after first time?

anasazi

Member
I have been battling with this one: after a good connect to my firmware project and reading all of the initial characteristics and data that is available and sending a couple of manager.WriteData to the device requesting new data to be ready, I do additional manager.ReadData2(s,c) calls and am expecting to see the fresh data. However all I get back is the same data from the first ReadData2 that was done just after connection.

On my device I do not see that anything was sent to it due to the manager.ReadData2(s,c) call in my B4A program which I assume would happen such that the fresh data my firmware device has updated for the requested characteristic would be brought over to the phone running the B4A application. Doesn't happen, I keep reading the same characteristic data from the first and only read that actually seemed to work. I don't see a manager.DataAvailable event after the first ones after connecting.

What am I missing on reading and re-reading a characteristic's data many times with out a disconnect/connection to make repeated reads to work?
My B4A program writes to the device and this works well. I can read data from the device which is pulled in just after initial connection.
I had tried using manager.ReadData(serviceID) which would read all of the characteristics and didn't see that this helped.

I think this question is more conceptual and someone else must be using this sort of reading chunks of data via. BLE ReadData2 calls.
So I didn't include any source code in my question at this point.
 

anasazi

Member
I should add to my question/query about the BLE2 ReadData2 that when I use BLEscanner app. from Google Play Store
this app. does indeed read and read again (when <R> is pressed) in the characteristics display panel. Fresh data appears
and in my firmware app. I increment a test string and reread on the phone, all is good. So I know my firmware does the
correct things to make a new data buffer available to be fetched by a BLE client device (phone/tablet/pc).
The device stays connected to the firmware as I can still use phone toggle buttons which flip LEDs on/off on the board.

Major symptom is no DAV event is raised after doing the ReadData or ReadData2 function calls, I can't fathom why?
Anybody had this experience and figure out how to get around it?
What is the normal way those functions are expected to work by the library author?

Thanks in advance!
 
Upvote 0

anasazi

Member
I have found a source version of the BLE2 library source code and it looks to me like there is a bug in ReadData2 which
would cause the absence of a DAV event for the reception of data after inquiry, I will post the section of code. I don't know
if this is that latest source for the included IDE library or now, there seems no version # in the source file.

In my firmware device I have a service with only 1 characteristic, a 132 byte data buffer registered and available to be read.
So, for this service UUID there is only ONE characteristic UUID so the part of the library code will NOT fire the DAV event as
that is in the next section past the if (atLeastOneReadable) { statement.

BLE2 library code fragment for ReadData2:
/*
 * Copyright 2010 - 2020 Anywhere Software (www.b4x.com)
 */
    /**
     * Asynchronously reads all characteristics from the given service. The DataAvailable will be raised when the data is available.
     */
    public void ReadData(String Service) {
        ReadData2(Service, null);                <<<==== THE NULL 2nd ARGUMENT WILL TRIGGER A DAV EVENT
    }
    /**
     * Asynchronously reads the value of the specified characteristic.
     *The DataAvailable will be raised when the data of this characteristic is available.
     */
    public void ReadData2(String Service, String Characteristic) {      <<<==== I CALL WITH 2 ARGUMENTS
        synchronized (charsToReadQueue) {
            boolean queueWasEmpty = charsToReadQueue.isEmpty();
            boolean atLeastOneReadable = false;
            BluetoothGattService ser = getService(Service);
            for (BluetoothGattCharacteristic chr : readableCharsFromService(ser)) {
                if (Characteristic == null || chr.getUuid().toString().equals(Characteristic)) {
                    charsToReadQueue.add(chr);       
                    atLeastOneReadable = true;
                }
            }
            if (atLeastOneReadable) {    <<<==== THE SERVICE I'M USING HAS ONE CHARACTERISTIC ONLY
                if (queueWasEmpty)
                    gatt.readCharacteristic(charsToReadQueue.get(0));   <<<==== SO THIS RUNS AND NO DAV EVENT IS TRIGGERED
            }
            else {
                if (Characteristic == null) {       <<<==== NOT NULL WITH 2 ARGUMENTS ====
                    Map data = new Map();
                    data.Initialize();
                    for (BluetoothGattCharacteristic chr : ser.getCharacteristics()) {
                        String uuid = chr.getUuid().toString();
                        data.Put(uuid, new byte[0]);
                    }
                    ba.raiseEventFromDifferentThread(BleManager2.this, null, 0, eventName + "_dataavailable", false,
                            new Object[] {Service, data});
                }
                else {
                    BA.LogInfo("No matching characteristic found.");
                }
            }
        }
    }

I think this is C++ code and am not all that understanding of the function calls it makes to the Android SDK but it appears to
me that if there is only one characteristic for a service UUID it will not fire the ba.raiseEventFromDifferentThread(BleManager2.this, null, 0, eventName + "_dataavailable" line which would thus trigger a call to my DAV event handler.

What does this call "getService(Service);" do?
And what does this one do "readableCharsFromService(ser)" do?

Shouldn't one of these 2 calls cause the phone BLE to communicate with the connected device and request new characteristic data to be returned by BLE radio medium? However I see no further radio comm. to the device after the initial ones after the "connect" call.

I'd like to stay out of the SDK, that's why I'm using B4A after all !
 
Upvote 0

anasazi

Member
Digging in more. The SDK file "BluetoothGatt.java" has a function that if called would flush the "cache" and cause a reread
of the characteristics specified I believe.
--------------------------------------------------------------------------------------
/**
* Clears the internal cache And forces a refresh of the services from the
* remote device.
* @hide
*/
Public boolean refresh(BluetoothDevice device) {
If (DBG) Log.d(TAG, "refresh() - device: " + device.getAddress());
If (mService == Null || mClientIf == 0) Return false;
Try {
mService.refreshDevice(mClientIf, device.getAddress());
} Catch (RemoteException e) {
Log.e(TAG,"",e);
Return false;
}
Return true;
}

--------------------------------------------------------------------------------------
This function looks like one I need to call prior to doing the ReadData2(serv,char) call to get a refresh of that data
to be brought over from the device I'm building. Wishing it were already in the BLE2 library.

So, I'm trying to figure out how to call the SDK function Java code directly from B4A. I'm sure others are doing this
so I'll also query the forum posts too.. (Side note ChatGPT came up with total garbage on this question...hehe..)
Thanks
(not sure why the 'code' insert thing dosen't work on this code fragment? sorry, I tried!)
 
Upvote 0

anasazi

Member
Solved with mod's to BleManager2. First added a function to clear the cache when my code knows it needs to read fresh data from the remote device, not rehashed cache data? Not sure how this library has been around for so many years with out anyone noticing this problem. Perhaps they fixed it but didn't say, I will share my findings here though. Second I made the case of a "service ID" that has only one "characteristic ID" produce a DAV event as it should have been doing. Here is the function for "zapCache(servID)" function:

zapCache(serviceID):
    /**
      * MY added - function to purge characteristic cache  so it will reload on ReadData()
      */
    public void    zapCache(String DeviceId){
        charsToReadQueue.clear();
               }

Then retooling the ReadData2() function so it behaves properly with the case of a "service ID" having
only one "characteristic ID" and returning a DAV (dataavailable) event for this:

Retake on ReadData2() to work with 1 characteristic case.:
/**
     * Asynchronously reads all characteristics from the given service. The DataAvailable will be raised when the data is available.
     */
    public void ReadData(String Service) {
        ReadData2(Service, null);
    }
    /**
     * Asynchronously reads the value of the specified characteristic.
     *The DataAvailable will be raised when the data of this characteristic is available.
     */
    public void ReadData2(String Service, String Characteristic) {
        synchronized (charsToReadQueue) {
            boolean queueWasEmpty = charsToReadQueue.isEmpty();
            boolean atLeastOneReadable = false;
            BluetoothGattService ser = getService(Service);
            for (BluetoothGattCharacteristic chr : readableCharsFromService(ser)) {
                if (Characteristic == null || chr.getUuid().toString().equals(Characteristic)) {
                    charsToReadQueue.add(chr);       
                    atLeastOneReadable = true;
                }
            }
            if (atLeastOneReadable) {
                if (queueWasEmpty){
/*                    gatt.readCharacteristic(charsToReadQueue.get(0));
*/
                    Map data = new Map();
                    data.Initialize();
                    for (BluetoothGattCharacteristic chr : ser.getCharacteristics()) {
                        String uuid = chr.getUuid().toString();
                        data.Put(uuid, new byte[0]);
                    }
                    ba.raiseEventFromDifferentThread(BleManagerMY.this, null, 0, eventName + "_dataavailable", false,
                            new Object[] {Service, data});
                }
            }
            else {
                if (Characteristic == null) {
                    Map data = new Map();
                    data.Initialize();
                    for (BluetoothGattCharacteristic chr : ser.getCharacteristics()) {
                        String uuid = chr.getUuid().toString();
                        data.Put(uuid, new byte[0]);
                    }
                    ba.raiseEventFromDifferentThread(BleManagerMY.this, null, 0, eventName + "_dataavailable", false,
                            new Object[] {Service, data});
                }
                else {
                    BA.LogInfo("No matching characteristic found.");
                }
            }

        }
    }

This solution works for me, just what I was expecting of the BLE library. Had to get into the Android
SDK after all, was hoping to avoid that. There might be a better solution to pursue too, but I'm happy
with this as I can pull fresh buffers of data from the device as expected now.
Cheers!
 
Upvote 0

anasazi

Member
There could be side effects, I'm still testing these mod's to the BLE library.

Anybody know where this code is located: " charsToReadQueue.clear();"?

I am wondering what this queue looks like and if I could just clear one service-UUID with
a given characteristic-UUID and not the entire cache/queue? I don't know how this queue
plays into the stream of data from the remote device, i.e. is it reading from a cached copy
of all of the UUID's that were read on connect() or simply a serial queue inline with data flowing
in from the BLE radio stream/remote device. Most likely in the Android.SDK files somewhere.

I found that clearing it as I did does indeed cause a BLE radio transfer of the exact item
I am requesting in the ReadData2(s,c) call, great. That wasn't happening. The data in the remote
device is constantly changing and I want the latest from a recent read.

It would be great to adapt the BleManager2 library ReadData/ReadData2 to have a additional
boolean argument which would be to purge that service/characteristic and force it to reread from
the remote device. I assumed (incorrectly) that this was automatic by just requesting as the library
is currently written. I'd rather see that than have to carry the modified source code to the BLE
library around with my B4A project source along with directions on how to compile it as a new
library replacement.

I would love one of you Java/B4X pro's out there to tackle this and make a clean solution!
 
Upvote 0

anasazi

Member
Ok, a day reading SDK docs, I suspect I'm violating the intent of BLE with this. I think this as it stands
requires a disconnect/reconnect to get new data as characteristic value which takes to long. I tried using
NOTIFICATION and found this only allows 20 bytes in a message. INDICATION is about the same. The BLE
server will have to loop and send the 100 byte messages using a 20 byte blocking method using NOTIFY or
INDICATE methods I suspect. From what I'm reading this seems to be the accepted method to pull larger
amounts of data from a BLE server to a BLE client device and stay with in the normal low power use of
the BLE devices. I should have designed my board using standard Bluetooth rather than the LE version.
Using gatt.refresh() I read is not recommended and could be unreliable as well as cause disconnects and
general failure in the data stream requiring more code to detect and amend these problems.
Ok, backing up a few steps on this one!
 
Upvote 0

emexes

Expert
Licensed User
read fresh data from the remote device, not rehashed cache data? Not sure how this library has been around for so many years with out anyone noticing this problem.

I had no problem reading changing values from a BLE pressure sensor, but that was several (ok... many) B4A versions back, so maybe something's gone awry in the meantime.

ps reading same sensor with equivalent code in B4I worked ok too
 
Upvote 0

anasazi

Member
Thanks for the note. I am finding that I can get a refresh of a characteristic data by the ReadData2((), however that data is available on the next call to the ReadData2(), so it is "late" by one read. For the first call I get the current data, then see BLE radio activity to my device and even the B4A dataavailable event triggers with the newer data, but at that time I have been handed back the previous cached data.

If you were just reading a sample over and over that might work and not be noticed. I'm trying to pull over a directory of file names which is a bit more complicated. Then pull a file's data over.

I set a boolean in the DataAvailable event to break a loop in my app. waiting for that to show a refresh after a read. However this can lock up my app. when there is only cached data being passed back and there is no actual data received from the BLE manager DAV event. Works sometimes and not others, I'll keep hammering to find something that works.

An example that works is the Android app. called "BLEscanner" we all test with, pressing the blue"R" reads the current data over and over as my firmware board changes data. I'm not sure what they are doing to get that to work. I refreshes fast as a human can press the R button.
Looking at my firmware debug print outs, it is not seeing a disconnect/reconnect but simply responds every time to the reads that that program is doing, just what I expect to happen. Wish the source code was available to look through on that.
 
Upvote 0

anasazi

Member
I was successful in calling the refresh function with this snippet placed in the BleManager2 library and recompiling it.
However even though it prints out a list of the BluetoothGatt.java functions and refresh(device) is one, it throws and exception
at run time saying it isn't found? Now sure on this one. This is a @Hide function in the SDK source so using the Method
one can declare it callable at run time, or that's the idea any way. (You have to add the reflector imports to the library also.
How to call android.bluetooth.BluetoothGatt functions in a library build if you must...:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

public void zapCache(String deviceId) throws Exception {
    BluetoothDevice device = this.devices.get(deviceId);
    Class<?> clazz = Class.forName("android.bluetooth.BluetoothGatt");
    Method[] allMethods = clazz.getDeclaredMethods();
        for (Method m : allMethods) {           /* for loop is extra for  */
        String mname = m.getName();             /* debug print of GaTT    */
        out.format("GATT has: %s()%n", mname);  /*  functions as a check? */
        };
    Method method = clazz.getDeclaredMethod("refresh", BluetoothGatt.class);
    method.setAccessible(true);   /* SDK hids the function, so we make it runabable */
    method.invoke(gatt, device);  /* off to the gatt.refresh(device), ignore return */
}
 
Upvote 0
Top