OpenTracksApp / OpenTracks

OpenTracks is a sport tracking application that completely respects your privacy.
https://OpenTracksApp.com
Apache License 2.0
1.04k stars 188 forks source link

Topeak PanoBike Heart Rate Monitor: needs some initial setup #782

Closed gordonel closed 3 years ago

gordonel commented 3 years ago

Describe the bug When Topeak PanoBike Heart Rate Monitor is paired to OpenTracks, it defaults to a "closed" sensor state, which prevents the sensor from transmitting any data. Apps like IpSensorMan can send the "Open" signal to the sensor, which eventually gets the sensor into a "tracking" state where it transmits data.

To Reproduce

  1. Go to Settings and pair the heart rate sensor
  2. Start recording the activity. You will see that the sensor doesn't come online and does not show the bpm value
  3. Use IpSensorMan to open the sensor (Add BLTE device > pair the device > long-press the device entry > tap Open)
  4. Return to OpenTracks. You should see that the sensor is now showing the bpm

Technical information

gordonel commented 3 years ago

I'm not much of a dev myself, but I'll be happy to help troubleshoot this if you tell me how

dennisguse commented 3 years ago

Can you post a screenshot showing the action to "open" the sensor via settings? The one's that I have are either on or off, but no further action (and even pairing is required).

And is this issue happening in the settings (sensor selection dialog) or while recording (i.e., no connection to sensor and thus no data)?

gordonel commented 3 years ago

@dennisguse I'll record a screengrab once I have a minute.

The problem is that I have to open the sensor in another app for it to work at all. I can pair the sensor just fine in OpenTracks, but unless I send the "open" signal to it via IpSensorMan, it won't work

gordonel commented 3 years ago

@dennisguse okay, here's the recording.

https://user-images.githubusercontent.com/45173186/118676713-4dd33780-b804-11eb-83df-c1800fa57455.mp4

From what I gather, IpSensorMan runs some sort of a service that makes the sensor discoverable and allows many apps to access it simultaneously. What's weird is that I tried a lot of apps and IpSensorMan is the only one that can talk to the sensor and make the other apps see it. Normally, I'd just connect the sensor to my device and be done with it, but here, well, I have to dance with a tambourine to make it work, basically

dennisguse commented 3 years ago

i guess, the sensor needs some (non-standard) initial setup before it sends data (send a message to the sensor). Gadgetbridge is doing something similar to get HR data from some smartwatches (or you have to start a workout from the smartwatch even).

You may check with with either IpSensorMan or the manufacturer of this HR sensor. To be honest: it is probably not worth the time to implement this as you have a (not cool) working solution.

gordonel commented 3 years ago

So normally, sensors just broadcast the data and OpenTracks doesn't do any set up/ping/whatever?

dennisguse commented 3 years ago

@gordonel Actually no.

After connecting and discovering service, we subscribe for updates on this specific characteristics by writing a specific value there. The characteristics is the "variable" (aka data) we would like to get informed about (different one per sensor type). One could also just get the data from characteristics (pull style), but that would be a waste of energy.

The responsible code is here: https://github.com/OpenTracksApp/OpenTracks/blob/9a7f24bddcbbce9ebe2e703c9c64350c9aed0a0f/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java#L91

And here are the addresses: https://github.com/OpenTracksApp/OpenTracks/blob/9a7f24bddcbbce9ebe2e703c9c64350c9aed0a0f/src/main/java/de/dennisguse/opentracks/util/BluetoothUtils.java#L40

gordonel commented 3 years ago
  1. Would pull style be worth trying out as a solution for this particular sensor?
  2. Maybe it's worth implementing support for the pull style as an optional advanced setting? The user could choose whether or not to pull, pull once then listen or pull always
dennisguse commented 3 years ago

Technically pull is possible, but it is not BLE style. And the effort within OpenTracks is considerable as we need to handle mulit-hreading for this.

And the issue might be that the sensor is not reporting any data without some specific treatment. Or only the subscription needs to be done differently.

... so pull might not even fix the problem.

gordonel commented 3 years ago

Okay, so to stop speculating and figure out how to make OpenTracks work, I need to figure out why this HRM needs activation in the first place, I guess. I'll continue researching and present my findings

gordonel commented 3 years ago

Okay. I think I figured it what IpSensorMan does using nRF Connect and got a bpm readout. The trouble is I can't find what's exactly missing in the code as it has all the UUID and settings needed to retrieve data. Do you see something I'm missing?

https://user-images.githubusercontent.com/45173186/118858610-058a4700-b8e2-11eb-9f7d-8b197a3252d9.mp4

EDIT: I also found out that after I do this, OpenTracks starts seeing the sensor too, so I no longer have to use a proprietary app 🥳

gordonel commented 3 years ago

I tried grabbing logs from the app via logcat and grepping them afterwards to search for bluetooth-specific lines and this is what I found. Let me know if you need more context, but what I did in this session was:

  1. Launch OpenTracks
  2. Pair the HRM
  3. Start an activity and let it run for 2 minutes I didn't get the bpm reading
    05-20 14:43:52.704 10595 10595 D BluetoothAdapter: isLeEnabled(): ON
    05-20 14:43:52.707 10595 10620 D BluetoothLeScanner: onScannerRegistered() - status=0 scannerId=6 mScannerId=0
    05-20 14:43:53.111 10595 10595 D BluetoothLeSensorPreference: Found device PanoBike BLE HRM ScanResult{device=90:59:AF:1F:77:20, scanRecord=ScanRecord [mAdvertiseFlags=6, mServiceUuids=[0000180d-0000-1000-8000-00805f9b34fb, 0000180f-0000-1000-8000-00805f9b34fb], mServiceSolicitationUuids=[], mManufacturerSpecificData={}, mServiceData={}, mTxPowerLevel=-2147483648, mDeviceName=PanoBike BLE HRM], rssi=-65, timestampNanos=1094237274473, eventType=27, primaryPhy=1, secondaryPhy=0, advertisingSid=255, txPower=127, periodicAdvertisingInterval=0}
    05-20 14:43:53.891 10595 10595 D BluetoothAdapter: isLeEnabled(): ON
    05-20 14:43:57.181 10595 10595 W BluetoothConnectionManager: Cannot disconnect if not connected.
    05-20 14:43:57.181 10595 10595 I BluetoothRemoteSensorManager: Connecting to bluetooth address: 90:59:AF:1F:77:20
    05-20 14:43:57.181 10595 10595 D BluetoothConnectionManager: Connecting to: 90:59:AF:1F:77:20
    05-20 14:43:57.181 10595 10595 D BluetoothGatt: connect() - device: 90:59:AF:1F:77:20, auto: true
    05-20 14:43:57.181 10595 10595 D BluetoothGatt: registerApp()
    05-20 14:43:57.181 10595 10595 D BluetoothGatt: registerApp() - UUID=62315931-31b5-45c4-94bb-4bd42b7dbbc0
    05-20 14:43:57.182 10595 10595 W BluetoothRemoteSensorManager: No Bluetooth address.
    05-20 14:43:57.182 10595 10595 W BluetoothConnectionManager: Cannot disconnect if not connected.
    05-20 14:43:57.182 10595 10595 W BluetoothRemoteSensorManager: No Bluetooth address.
    05-20 14:43:57.182 10595 10595 W BluetoothConnectionManager: Cannot disconnect if not connected.
    05-20 14:43:57.182 10595 10595 W BluetoothRemoteSensorManager: No Bluetooth address.
    05-20 14:43:57.182 10595 10595 W BluetoothConnectionManager: Cannot disconnect if not connected.
    05-20 14:43:57.183 10595 10620 D BluetoothGatt: onClientRegistered() - status=0 clientIf=6
    05-20 14:44:39.272 10595 10620 D BluetoothGatt: onClientConnectionState() - status=0 clientIf=6 device=90:59:AF:1F:77:20
    05-20 14:44:39.272 10595 10620 D BluetoothConnectionManager: Connected to sensor: 90:59:AF:1F:77:20
    05-20 14:44:39.273 10595 10620 D BluetoothGatt: discoverServices() - device: 90:59:AF:1F:77:20
    05-20 14:44:39.717 10595 10620 D BluetoothGatt: onConnectionUpdated() - Device=90:59:AF:1F:77:20 interval=6 latency=0 timeout=500 status=0
    05-20 14:44:40.021 10595 10620 D BluetoothGatt: onSearchComplete() = Device=90:59:AF:1F:77:20 Status=0
    05-20 14:44:40.022 10595 10620 D BluetoothGatt: setCharacteristicNotification() - uuid: 00002a37-0000-1000-8000-00805f9b34fb enable: true
    05-20 14:44:41.050 10595 10620 D BluetoothGatt: onConnectionUpdated() - Device=90:59:AF:1F:77:20 interval=960 latency=0 timeout=600 status=0
    05-20 14:44:50.095 10595 10620 D BluetoothGatt: onClientConnectionState() - status=8 clientIf=6 device=90:59:AF:1F:77:20
    05-20 14:44:50.095 10595 10620 D BluetoothConnectionManager: Disconnected from sensor: 90:59:AF:1F:77:20
    05-20 14:45:36.472 10595 10620 D BluetoothGatt: onClientConnectionState() - status=0 clientIf=6 device=90:59:AF:1F:77:20
    05-20 14:45:36.472 10595 10620 D BluetoothConnectionManager: Connected to sensor: 90:59:AF:1F:77:20
    05-20 14:45:36.472 10595 10620 D BluetoothGatt: discoverServices() - device: 90:59:AF:1F:77:20
    05-20 14:45:36.912 10595 10620 D BluetoothGatt: onConnectionUpdated() - Device=90:59:AF:1F:77:20 interval=6 latency=0 timeout=500 status=0
    05-20 14:45:37.566 10595 10620 D BluetoothGatt: onSearchComplete() = Device=90:59:AF:1F:77:20 Status=0
    05-20 14:45:37.566 10595 10620 D BluetoothGatt: setCharacteristicNotification() - uuid: 00002a37-0000-1000-8000-00805f9b34fb enable: true
    05-20 14:45:38.649 10595 10620 D BluetoothGatt: onConnectionUpdated() - Device=90:59:AF:1F:77:20 interval=960 latency=0 timeout=600 status=0
    05-20 14:45:47.693 10595 10620 D BluetoothGatt: onClientConnectionState() - status=8 clientIf=6 device=90:59:AF:1F:77:20
    05-20 14:45:47.693 10595 10620 D BluetoothConnectionManager: Disconnected from sensor: 90:59:AF:1F:77:20
    05-20 14:46:00.809 10595 10805 D BluetoothGatt: onClientConnectionState() - status=0 clientIf=6 device=90:59:AF:1F:77:20
    05-20 14:46:00.809 10595 10805 D BluetoothConnectionManager: Connected to sensor: 90:59:AF:1F:77:20
    05-20 14:46:00.809 10595 10805 D BluetoothGatt: discoverServices() - device: 90:59:AF:1F:77:20
    05-20 14:46:01.253 10595 10805 D BluetoothGatt: onConnectionUpdated() - Device=90:59:AF:1F:77:20 interval=6 latency=0 timeout=500 status=0
    05-20 14:46:01.587 10595 10805 D BluetoothGatt: onSearchComplete() = Device=90:59:AF:1F:77:20 Status=0
    05-20 14:46:01.587 10595 10805 D BluetoothGatt: setCharacteristicNotification() - uuid: 00002a37-0000-1000-8000-00805f9b34fb enable: true
    05-20 14:46:02.045 10595 10595 D BluetoothGatt: close()
    05-20 14:46:02.045 10595 10595 D BluetoothGatt: unregisterApp() - mClientIf=6
    05-20 14:46:02.046 10595 10595 W BluetoothConnectionManager: Cannot disconnect if not connected.
    05-20 14:46:02.046 10595 10595 W BluetoothConnectionManager: Cannot disconnect if not connected.
dennisguse commented 3 years ago

This is the important part:

05-20 14:45:37.566 10595 10620 D BluetoothGatt: setCharacteristicNotification() - uuid: 00002a37-0000-1000-8000-00805f9b34fb enable: true
05-20 14:45:38.649 10595 10620 D BluetoothGatt: onConnectionUpdated() - Device=90:59:AF:1F:77:20 interval=960 latency=0 timeout=600 status=0
05-20 14:45:47.693 10595 10620 D BluetoothGatt: onClientConnectionState() - status=8 clientIf=6 device=90:59:AF:1F:77:20
05-20 14:45:47.693 10595 10620 D BluetoothConnectionManager: Disconnected from sensor: 90:59:AF:1F:77:20

We are connected and try to write the "please notify" field of the characteristics and then we are disconnected after about 10s. This should not happen... do you have another Android device to test with? And Bluetooth snooping might reveal something.

gordonel commented 3 years ago

No, but I can try bluetooth snooping. As a mitigation, would it be possible to bump the timeout to something like 360 seconds and log how much it actually takes? I'll try to do it on my own tomorrow 'cause I got to do some work, but if you can make a branch I could build off of faster, that would be greatly appreciated 🙏🏻

EDIT: just re-tried subscribing via nRF Connect and it doesn't take more than 3 seconds to get a reading

gordonel commented 3 years ago

Couldn't find a hardcoded timeout anywhere here, but I did try on another Pixel and had no luck on it either

gordonel commented 3 years ago

I should clarify that I'm using an F-Droid version, not PlayStore version. I'll try building on my own to see if there's something funny going on with the F-Droid build

UPD: built 3.18.1-debug from source, same thing, no luck

gordonel commented 3 years ago

Did some bluetooth snooping, but couldn't find any HRM-related errors or anything. Here's the write request that should subscribe us

Frame 451: 14 bytes on wire (112 bits), 14 bytes captured (112 bits)
    Encapsulation type: Bluetooth H4 with linux header (99)
    Arrival Time: May 21, 2021 15:10:23.439703000 EEST
    [Time shift for this packet: 0.000000000 seconds]
    Epoch Time: 1621599023.439703000 seconds
    [Time delta from previous captured frame: 0.006631000 seconds]
    [Time delta from previous displayed frame: 0.006631000 seconds]
    [Time since reference or first frame: 244.770882000 seconds]
    Frame Number: 451
    Frame Length: 14 bytes (112 bits)
    Capture Length: 14 bytes (112 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    Point-to-Point Direction: Sent (0)
    [Protocols in frame: bluetooth:hci_h4:bthci_acl:btl2cap:btatt]
Bluetooth
    [Source: REDACTED]
    [Destination: TexasIns_1f:77:20 (90:59:af:1f:77:20)]
Bluetooth HCI H4
    [Direction: Sent (0x00)]
    HCI Packet Type: ACL Data (0x02)
Bluetooth HCI ACL Packet
    .... 0000 0000 0011 = Connection Handle: 0x003
    ..00 .... .... .... = PB Flag: First Non-automatically Flushable Packet (0)
    00.. .... .... .... = BC Flag: Point-To-Point (0)
    Data Total Length: 9
    Data
    [Connect in frame: 365]
    [Disconnect in frame: 456]
    [Source BD_ADDR: REDACTED]
    [Source Device Name: REDACTED]
    [Source Role: Unknown (0)]
    [Destination BD_ADDR: TexasIns_1f:77:20 (90:59:af:1f:77:20)]
    [Destination Device Name: PanoBike BLE HRM]
    [Destination Role: Unknown (0)]
    [Current Mode: Unknown (-1)]
Bluetooth L2CAP Protocol
    Length: 5
    CID: Attribute Protocol (0x0004)
Bluetooth Attribute Protocol
    Opcode: Write Request (0x12)
        0... .... = Authentication Signature: False
        .0.. .... = Command: False
        ..01 0010 = Method: Write Request (0x12)
    Handle: 0x0013 (Heart Rate: Heart Rate Measurement: Client Characteristic Configuration)
        [Service UUID: Heart Rate (0x180d)]
        [Characteristic UUID: Heart Rate Measurement (0x2a37)]
        [UUID: Client Characteristic Configuration (0x2902)]
    Characteristic Configuration Client: 0x0001, Notification
        0000 0000 0000 00.. = Reseved: 0x0000
        .... .... .... ..0. = Indication: False
        .... .... .... ...1 = Notification: True

And here's a write response:

Frame 453: 10 bytes on wire (80 bits), 10 bytes captured (80 bits)
    Encapsulation type: Bluetooth H4 with linux header (99)
    Arrival Time: May 21, 2021 15:10:23.457462000 EEST
    [Time shift for this packet: 0.000000000 seconds]
    Epoch Time: 1621599023.457462000 seconds
    [Time delta from previous captured frame: 0.009205000 seconds]
    [Time delta from previous displayed frame: 0.009205000 seconds]
    [Time since reference or first frame: 244.788641000 seconds]
    Frame Number: 453
    Frame Length: 10 bytes (80 bits)
    Capture Length: 10 bytes (80 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    Point-to-Point Direction: Received (1)
    [Protocols in frame: bluetooth:hci_h4:bthci_acl:btl2cap:btatt]
Bluetooth
    [Source: TexasIns_1f:77:20 (90:59:af:1f:77:20)]
    [Destination: REDACTED]
Bluetooth HCI H4
    [Direction: Rcvd (0x01)]
    HCI Packet Type: ACL Data (0x02)
Bluetooth HCI ACL Packet
    .... 0000 0000 0011 = Connection Handle: 0x003
    ..10 .... .... .... = PB Flag: First Automatically Flushable Packet (2)
    00.. .... .... .... = BC Flag: Point-To-Point (0)
    Data Total Length: 5
    Data
    [Connect in frame: 365]
    [Disconnect in frame: 456]
    [Source BD_ADDR: TexasIns_1f:77:20 (90:59:af:1f:77:20)]
    [Source Device Name: PanoBike BLE HRM]
    [Source Role: Unknown (0)]
    [Destination BD_ADDR: REDACTED]
    [Destination Device Name: REDACTED]
    [Destination Role: Unknown (0)]
    [Current Mode: Unknown (-1)]
Bluetooth L2CAP Protocol
    Length: 1
    CID: Attribute Protocol (0x0004)
Bluetooth Attribute Protocol
    Opcode: Write Response (0x13)
        0... .... = Authentication Signature: False
        .0.. .... = Command: False
        ..01 0011 = Method: Write Response (0x13)
    [Handle: 0x0013 (Heart Rate: Heart Rate Measurement: Client Characteristic Configuration)]
        [Service UUID: Heart Rate (0x180d)]
        [Characteristic UUID: Heart Rate Measurement (0x2a37)]
        [UUID: Client Characteristic Configuration (0x2902)]
    [Request in Frame: 451]

LMK if you need other packets or the whole dump

gordonel commented 3 years ago

@dennisguse I'm no expert, but nothing screams Error here. That said, I'm unable to test or purchase another heart rate monitor due to country-wide heart rate monitor shortage that's driven by the current COVID craze. What are your thoughts on getting this issue back into potential bug realm and what other troubleshooting should I try?

dennisguse commented 3 years ago

@gordonel I am also clueless (henice I did not yet reply yet). Everything looks okay... and then the sensor disconnects for no obvious reason. Using nRF or IpSensorMan it works. The last thing, we do is writing the characteristic and then boom aka nothing.

Next that you could try via OpenTracks:

Out of curiosity: could you try RunnerUp and FitoTrack (if I remember correctly they use a lib to connect to sensors)?

PS: how did you do the package snooping?

PPS: F-Droid and PlayStore built are identical except that the PlayStore builds are available earlier and you have to trust my computer :)

gordonel commented 3 years ago

Put a lot of breakpoints into the BluetoothConnectionManager and see what happens. It might be that we are doing something too quickly.

I'd have to learn how to do that, so it might take some time

Out of curiosity: could you try RunnerUp and FitoTrack

RunnerUP connects to the sensor and shows the readout without problems. FitoTrack doesn't have a settings entry for sensors and crashes when I try to start a workout session

how did you do the package snooping?

Bluetooth HCI logs from dev options + wireshark

dennisguse commented 3 years ago

RunnnerUp might be a good hint (see AndroidBLEHRProvider). They do a descriptor read before trying to enable notifications.

I hacked something: https://github.com/OpenTracksApp/OpenTracks/tree/hr%23782 Can you give this a try?

RunnerUp code:

            BluetoothGattDescriptor mHRMccc = mHRMcharac.getDescriptor(CCC);
            if (mHRMccc == null) {
                reportConnectFailed("CCC for HEART RATE MEASUREMENT charateristic not found!");
                return;
            }
            if (!btGatt.readDescriptor(mHRMccc)) {

and they enable the notifications in the following callback


        @Override
        public void onDescriptorRead(BluetoothGatt gatt,
                                     BluetoothGattDescriptor arg0, int status) {

            BluetoothGattCharacteristic mHRMcharac = arg0.getCharacteristic();
            if (!enableNotification(true, mHRMcharac)) {
                reportConnectFailed("Failed to enable notification in onDescriptorRead");
            }
        }
dennisguse commented 3 years ago

@gordonel Any updates?

gordonel commented 3 years ago

@gordonel Any updates?

Hey. Sorry for radio silence. Busy with work. This is on my plate, don't worry

gordonel commented 3 years ago

@dennisguse I'm back. Sorry for the delay.

Nothing on your branch, unfortunately

dennisguse commented 3 years ago

@gordonel I meant this commit: https://github.com/OpenTracksApp/OpenTracks/commit/b22d555e5750edbb350098040dee468439ff55ef It is on this branch: https://github.com/OpenTracksApp/OpenTracks/tree/hr%23782

gordonel commented 3 years ago

@gordonel I meant this commit: b22d555 It is on this branch: https://github.com/OpenTracksApp/OpenTracks/tree/hr%23782

Yes, I built from this commit

dennisguse commented 3 years ago

@gordonel Then I am (again) out of options/ideas. So, I guess you are on your own... It might be some kind of timing issue, and you might be able to analyze by putting some breakpoints into BluetoothConnectionManager.

gordonel commented 3 years ago

@gordonel Then I am (again) out of options/ideas. So, I guess you are on your own... It might be some kind of timing issue, and you might be able to analyze by putting some breakpoints into BluetoothConnectionManager.

That's unfortunate. Please don't delete the branch. I'll try to work on top of it

dennisguse commented 3 years ago

@gordonel Please re-open, if you are starting to work on this again.