Skip to content

Conversation

@dennisguse
Copy link

@dennisguse dennisguse commented Dec 30, 2021

Fixes #895.

Not tested due to lack of device.
Did not test compiling locally :)

@yehoshuapw
Copy link
Contributor

I can't tell if it's doing anything different - but I built and pushed this change, and nothing is broken.

@trman
Copy link

trman commented Dec 31, 2021

i have tested and don't seem to resolve the initial issue :
opentrack 3.23 don't seen this pr infinitime as hr device

@geekbozu geekbozu added the needs testing Needs testing on a physical device label Dec 31, 2021
@dennisguse
Copy link
Author

@trman Thanks for testing!
The UUID should be 0000180d-0000-1000-8000-00805f9b34fb and I hope I encoded it correctly.
I came up with 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x0D, 0x18, 0x00, 0x00.

What I did:

package de.dennisguse.opentracks.util;

import static org.junit.Assert.assertEquals;

import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.nio.ByteBuffer;
import java.util.UUID;

@RunWith(AndroidJUnit4.class)
public class UUIDUtilsTest {

    @Test
    public void test() {

        byte[] dfuUUID = new byte[]{0x23, (byte) 0xD1, (byte) 0xBC, (byte) 0xEA, 0x5F, 0x78, 0x23, 0x15, (byte) 0xDE, (byte) 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00};
        assertEquals("00001530-1212-efde-1523-785feabcd123", fromBytes(reverse(dfuUUID)).toString());

        assertEquals(toString(new byte[]{(byte) 0xFB, 0x34, (byte) 0x9B, 0x5F, (byte) 0x80, 0x00, 0x00, (byte) 0x80, 0x00, 0x10, 0x00, 0x00, 0x0D, 0x18, 0x00, 0x00}), toString(reverse(toBytes(BluetoothUtils.HEART_RATE_SERVICE_UUID))));
        assertEquals(BluetoothUtils.HEART_RATE_SERVICE_UUID, fromBytes(reverse(new byte[]{(byte) 0xFB, 0x34, (byte) 0x9B, 0x5F, (byte) 0x80, 0x00, 0x00, (byte) 0x80, 0x00, 0x10, 0x00, 0x00, 0x0D, 0x18, 0x00, 0x00})));
    }

    public static String toString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append("0x" + String.format("%02X ", b));
        }
        return  sb.toString();
    }

    public static byte[] reverse(byte[] bytes) {
        byte[] reversed = new byte[bytes.length];
        for (int i = 0; i < bytes.length; i++) {
            reversed[bytes.length - 1 - i] = bytes[i];
        }
        return reversed;
    }

    public static UUID fromBytes(byte[] bytes) {
        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
        long mostSignificant = byteBuffer.getLong();
        long lestSignificant = byteBuffer.getLong();
        return new UUID(mostSignificant, lestSignificant);
    }

    public static byte[] toBytes(@NonNull UUID uuid) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(16);
        byteBuffer.putLong(uuid.getMostSignificantBits());
        byteBuffer.putLong(uuid.getLeastSignificantBits());
        return byteBuffer.array();
    }
}

Disclaimer: Again lack of test device and I never used nimble and don't have local setup to compile Infinitime.

@dennisguse
Copy link
Author

Side note: I don't know if Android parses all service UUIDs from an ADV packet.
Never had any chance to test it :/

@Avamander
Copy link
Collaborator

It's usually not Android, it's the app - that can then reason about a device. Though instead of advertising more, OpenTracks could check for the service and use it if found.

@dennisguse
Copy link
Author

@Avamander
Copy link
Collaborator

It is true, you can filter beacons for the devices you're looking for and then initiating a service discovery. Alternatively, let the user pick a device and then do it. Costly or not, it doesn't matter in that case.

@trman
Copy link

trman commented Dec 31, 2021

so , @dennisguse , @JF002 , here another test with the nightly of opentracks (add an toogle in order to not filter uuid of bluetooth device when searching hr devices)

case infinitime is securely connected with gadgetbridge:

  • uuid filtered : infinitime not listed
  • uuid not filtered : infinitime not seen by opentracks

case infinitime is disconnected of gadgetbridge:

  • uuid filtered : infinitime not listed
  • uuid not filterd : infinitime seen by opentrack and usable but fc report only when infinitime screen is ON (otherwise it's 0)

for what is see , it can show three things:

  1. the uuid is not accepted by opentracks
  2. if infinitime is connected securely to gadgetbridge , opentracks can't see infinitime as bluetooth device (HR or not)
  3. if infinitime is connected to opentracks (gadgetbridge is not connected) , the hr task is not functional/usable when the screen is off in infinitime and that make infinitime useless as hr device

@dennisguse
Copy link
Author

@trman That is expected - most devices do not send AVD when connected to some other application/ device.
Could you also test if you only AVD the heart rate service UUID?

PS/ RunnerUp also filters by AVD UUID: https://github.com/jonasoreland/runnerup

@trman
Copy link

trman commented Jan 1, 2022

@trman That is expected - most devices do not send AVD when connected to some other application/ device. Could you also test if you only AVD the heart rate service UUID?

PS/ RunnerUp also filters by AVD UUID: https://github.com/jonasoreland/runnerup

Hi , @dennisguse , @JF002

Since @dennisguse , said RunnerUp also filters by AVD UUID

so here is the test with RunnerUp (from fdroid) :

case infinitime is securely connected with gadgetbridge:
-infinitime seen by RunnerUp and usable but fc report only when infinitime screen is ON (otherwise it's 0)

case infinitime is disconnected of gadgetbridge:
-infinitime is automaticaly connected as hrm device but when searching hrm device oen option int's not found

for what is see , it can show two things:

  1. the uuid seem to be accepted by RunnerUp
  2. if infinitime is connected securely to gadgetbridge , RunnerUp do accept infinitme as hr device on spot
    but hr is not functional/usable when the screen is off in infinitime and that make infinitime nearly useless as hr device

it's seem that RunnerUp do/may use another way (that complement/replace uuid) to connect to hr device , (and that way work without the need to disconnect of gadgetbridge) maybe opentracks dev should use a similar way

it may be related to the CompanionDevice API ( that gadgetbridge use as well) that allow to share the fc to other apps

@Avamander
Copy link
Collaborator

I guess in the end this PR's behaviour is fine, but I have a few small concerns with how it'll behave in the future.

The PR's behaviour would be nice to test with the encrypted+secured connection flag on the characteristic, because that is on the future roadmap to only allow secure methods of accessing the PT. It doesn't have to be done now, but it seems like it might break.

@Avamander
Copy link
Collaborator

@trman
Companion device pairing (CDM) API only sets a specific system database flag that grants the app extra rights (which allows better resistance to OOM kills). It should not affect pairing/bonding, it's not a (pre)requisite.

GB simply does service discovery for each interesting device, attaches itself to what it can understand. It doesn't care too much about only the adv. beacon like the other apps in question.

@trman
Copy link

trman commented Jan 1, 2022

well @Avamander as far this new uuid seem concerned , runnerup is more up to task for the secure connection than opentracks (at least for now ) with the new uuid

the next version of opentrack may allow to search device device without filtering

@trman
Copy link

trman commented Jan 1, 2022

Companion device pairing (CDM) API only sets a specific system database flag that grants the app extra rights (which allows
better resistance to OOM kills). It should not affect pairing/bonding, it's not a (pre)requisite.

@Avamander it seem you are mistaken about the paring : as per this https://developer.android.com/guide/topics/connectivity/companion-device-pairing and https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Companion-Device-pairing
this show that the pairing is faster , more stable and easier with companion device.

it's not a pre requirement but it seem allow a faster connection with it than without.

GB simply does service discovery for each interesting device, attaches itself to what it can understand. It doesn't care too much about only the adv. beacon like the other apps in question.

i only written what i have see when gadgetbrigde is connected or not with opentrack and runnerup .

And it seem that it have some impact on opentracks but not in runnerup when GB is connected.

@Avamander
Copy link
Collaborator

@trman Well in some ways CDM is "faster" and "more secure" but not in the ways you're thinking or we're talking. Stability wise, definitely not better, sucks major **** with dual-protocol devices.

@JF002
Copy link
Collaborator

JF002 commented Jan 2, 2022

Gentlemen, please try to stay focus on the topic of this PR : add the HR service UUID in the advertising package so companion apps can detect the PineTime as a HR device without having to start the service discovery procedure.

InfiniTime allows only 1 connection (that's why opentracks cannot see the PineTime once it's connected to Gadgetbridge), does the HR acquisition only when the display is ON and do not implement CDM. Those are known behaviors and out of the scope of this PR. Please open new issues or comment the existing ones if you want to talk about those topics.

In my opinion, the only info we need here is to check that the UUID this PR adds to the advertising packet is the one expected by companion apps to detect a HR sensor.

@trman
Copy link

trman commented Jan 2, 2022

In my opinion, the only info we need here is to check that the UUID this PR adds to the advertising packet is the one expected by companion apps to detect a HR sensor.

as far i'am concerned , @JF002 , i have done an exhaustive tests that allow to check if uid is read in opentracks (and as requested runnerup)

InfiniTime allows only 1 connection (that's why opentracks cannot see the PineTime once it's connected to Gadgetbridge), does the HR acquisition only when the display is ON and do not implement CDM. Those are known behaviors and out of the scope of this PR. Please open new issues or comment the existing ones if you want to talk about those topics.

in order to not have misunderstanding , i'am testing with gadgetbridge and without because user might not understand why it's not working when gadgetbridge is activated.
in fact for the unfiltered opentrack , at first i didn't understand that gadgetbridge the one which was blocking !

so what you think might be 'obvious' , can very well not be obvious to user....

the same can be said for the hr active only screen ON , because , at first when i tested with fitotrack i was astonished it didn't work when it was said that infinitme was a hr device....
Since every smartwatch that i have worn that say that they act as hr device work when screen is OFF...

@trman
Copy link

trman commented Jan 2, 2022

in order to have a conclusives tests, i will add shortly a test with opentracks and runnerup without this pr (well it's more for runnerup actually ) in order to check if runnerup do retreive the adv beacon or not

@trman
Copy link

trman commented Jan 2, 2022

here is the test WITHOUT this pr @JF002 :

so here is the test with RunnerUp (from fdroid) :

case infinitime is securely connected with gadgetbridge:
-infinitime is automaticaly connected as hrm device but when searching hrm device oen option int's not found

case infinitime is disconnected of gadgetbridge:
-infinitime is automaticaly connected as hrm device but when searching hrm device oen option int's not found

@trman
Copy link

trman commented Jan 2, 2022

@JF002 @dennisguse
so as far runner up is concerned , it don't need to have this pr in order connect to infinitme as hr device

@dennisguse
Copy link
Author

@trman this patch would just enable OpenTracks to search for the device for device selection; once the MAC address is stored, the ADV packages are not used anymore.
Moreover, RunnerUp checks for bonded aka paired devices - OpenTracks doesn't do this for device discovery.
Don't know if OpenTracks should though.

Relevant code from RunnerUp:
AndroidBLEHRProvider.java:

            // Android 4.3
            btAdapter.startLeScan(mLeScanCallback);
        else {
            if (mSupportPaired) {
                for (BluetoothDevice btDeviceThis : btAdapter.getBondedDevices()) {
                    if (btDeviceThis.getType() != BluetoothDevice.DEVICE_TYPE_LE) {
                        log("Ignoring paired non BLE device: " + btDeviceThis.getName());
                        continue;
                    }

                    // Paired device, for instance Huami/Amazfit Bip S could be supported
                    log("Trying paired generic BLE device: " + btDeviceThis.getName());
                    mLeScanCallback.onLeScan(btDeviceThis, 0, null);
                }
            }
            btAdapter.startLeScan(SCAN_UUIDS, mLeScanCallback);
        }

@JF002
Copy link
Collaborator

JF002 commented Jan 2, 2022

@trman I'll repeat for the last time : we know that InfiniTime accepts only 1 connection, we know InfiniTime acquires HR data only when the display is ON. This is confusing for you and other users, right. So let's talk about those topics in their respective issues and/or PR. This PR does not cover these points so please STOP mentionning them in each and every one of your comment. It only add noise and confusion in this simple "5 lines" PR.

And I also repeat : the only thing that matters here is : are companion apps that only parse the ADV packet (like OpenTracks) able to detect InfiniTime as a HR device. Which means that RunnerUp is not covered by this PR as it checks for paired device.

@trman
Copy link

trman commented Jan 2, 2022

And I also repeat : the only thing that matters here is : are companion apps that only parse the ADV packet (like OpenTracks) able to detect InfiniTime as a HR device. Which means that RunnerUp is not covered by this PR as it checks for paired device.

@JF002 id'nt know this : it was dennisguse that said that runnerup was using uuid , otherwise i would not have bothered do a test for it
#897 (comment)

PS/ RunnerUp also filters by AVD UUID: https://github.com/jonasoreland/runnerup

@trman
Copy link

trman commented Jan 2, 2022

my conclusion after all these test :
for the opentracks : the pr not working for the filtered search in order to find infinitme as hr device (as for now) @JF002 @dennisguse

@dennisguse
Copy link
Author

How can we proceed from here?
I don't if this an issue with the provided UUID (I might gotten something wrong), the Android BLE discovery subsystem, or something else.
Any suggestions?

@trman Could you record an ADV package (e.g.g, using bluetoothctl) and check if it looks okay?

PS/ today I ordered a PineTime, but will take up to 6 weeks 🤷

@JF002
Copy link
Collaborator

JF002 commented Jan 7, 2022

@dennisguse Sorry for the delay, I got busy on other things and on releasing 1.8 :)

So, I did some test, and I have to admit I'm a bit confused: Using your branch, the display would display nothing... I'm not sure why...

Then I applied your changes on current develop, and InfiniTime ran correctly, but advertising did not seem right.
Here is a screenshot of NRFConnect showing the advertising of InfiniTime without your changes:
image

And the same screenshot with your changes:
image

As you can see, there UUIDs disappeared completely, which is obviously not what we expected.
I guess we'll have to dig a bit in NimBLE to see how to manage multiple UUIDs in the advertissement...

I also tried by adding only the HR UUID to check that it works correctly with OpenTracks : it works!
NRFConnect show the UUID:
image

And OpenTracks lists my PineTime even when "Only show Bluetooth devices that announce required services" is enabled 👍

@dennisguse
Copy link
Author

@JF002 No worries; thanks for testing!

@JF002
Copy link
Collaborator

JF002 commented Jan 7, 2022

I'll try to do more tests when I get the time.
@evergreen22 @geekbozu any idea about this issue?

@dennisguse
Copy link
Author

Side not: the heart rate service UUID is already defined in HeartRateService.h and should probably be taken from there.

And I checked the nimble code and the relevant code that prepares the ADV is this function ble_hs_adv_set_array_uuid128.

@dennisguse
Copy link
Author

@JF002 I now got a sealed PineTime and could do some testing.
Should we continue this or postpone this PR?

@JF002
Copy link
Collaborator

JF002 commented Jan 25, 2022

@dennisguse We can of course continue to work on this PR. Unfortunately, I haven't had the opportunity to do more tests since my last comment :/

@trman
Copy link

trman commented Feb 7, 2022

@JF002 I now got a sealed PineTime and could do some testing.

@dennisguse did you have tried to do , with your sealed pinetme , some test of your code?
if don't and before you do , you should change (if it's this not already installed) the bootloader (and recovery) with this https://github.com/InfiniTimeOrg/pinetime-mcuboot-bootloader/releases/tag/1.0.0

it would make recover the dfu much easier if the dfu installed go wrong

@dennisguse
Copy link
Author

@trman Sadly, no and time is at a premium the next 2-3 month.
I hope I have some time to fix some ugly things in OpenTracks, but I doubt that I will have time to do this properly.
Thanks for the tip - and luckily the new bootloader was already installed :)

@dennisguse dennisguse closed this Feb 8, 2024
@florisre
Copy link

florisre commented Jul 3, 2024

Anybody willing to pick this up again? I am happy to help in testing!

@mark9064 mark9064 mentioned this pull request Jul 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs testing Needs testing on a physical device

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose the HeartRate service UUID in BLE ADV packet

7 participants