Jump to content

[Typescript] translate bluetooth DataView notifications to usable data.

Hello! This may be a bit long winded, but bare with me. 

 
TLDR: Looking for ways of parsing binary response from a Bluetooth device.
 
So, I've got a crazy idea (maybe) in that I've been trying to develop a data logging application and fitness tracker for my new IC4 bike. The difference between this app and others available on the market is that it works via the experimental Bluetooth browser standard. Described in more detail here. Web Bluetooth API - Web APIs | MDN (mozilla.org).
 
Getting to the point. I've paired to my Bluetooth connection (server). Parsed through the services available and exposed by using a Bluetooth discovery application on my phone. Again, pairing to the bike. I'm able to see what I can retrieve, notify, read, write etc. But I don't know how to translate that on a web browser?
 
Why might that be? Well, since the standard is relatively new. When I subscribe to notifications from the bike via Bluetooth. On the characteristic CSC Measurement I get a little endian binary 88 bit response that I can't seem to figure out where the data indicated above lies. It's not auto-magical like the above screenshot on my phone would indicate.
 
For example it will look something like this when subscribing to notifications on the WEB for CSC Measurement.
 
hex: 0x593400e04890
binary: 11001011011010000000000111000000100000011100000
 
What I do know so far is that the times in milliseconds reported are unsigned 16 bit integers.
That lets me guess and check where in the sequence those measurements may be in the response. However rather than guessing and checking I figured I'd just ask if there's an easier way to translate the data received. So, I can use it in my web application.
Figured it my android device can parse the Bluetooth notifications then so can I. Also, I've already emailed the manufacturer if there's a data sheet describing the notifications.
 
import { Injectable } from '@angular/core';
import { SchwinIc4BluetoothCharacteristics, SchwinIc4BluetoothServices } from '@solid-octo-couscous/model';
import { BaseBluetoothConnectionService } from './base-bluetooth-connection.service';

declare const navigator: Navigator;

@Injectable()
export class SchwinIc4BluetoothConnectionService extends BaseBluetoothConnectionService {
	constructor() {
		super(
			[{ name: 'IC Bike' }],
			[
				SchwinIc4BluetoothServices.cyclingSpeedAndCadence,
				SchwinIc4BluetoothServices.deviceInformation,
				SchwinIc4BluetoothServices.fitnessMachine,
				SchwinIc4BluetoothServices.genericAccess,
				SchwinIc4BluetoothServices.heartRate,
			]
		);
	}

	public async connectToCyclingSpeedAndCadenceService(): Promise<void> {
		const primaryBluetoothServices = await this.connectToSchwinBike();
		const cyclingSpeedCadenceService = primaryBluetoothServices?.find(
			service => service.uuid === SchwinIc4BluetoothServices.cyclingSpeedAndCadenceUUID
		);
		const cscMeasurementChararacteristic:
			| BluetoothRemoteGATTCharacteristic
			| undefined = await cyclingSpeedCadenceService?.getCharacteristic(
			// CSC Measurement feature. csc = cycling speed cadence.
			SchwinIc4BluetoothCharacteristics.cscMeasurement
		);

		const result:
			| BluetoothRemoteGATTCharacteristic
			| undefined = await cscMeasurementChararacteristic?.startNotifications();
		result?.addEventListener('characteristicvaluechanged', this.parseCadenceWheelSpeedWheelTime);
	}

	private readonly connectToSchwinBike = async (): Promise<BluetoothRemoteGATTService[]> => {
		// TODO: add a check in here to notify the user if they're using a non-supported browser.
		const userSelectedSchwinIc4Bike: BluetoothDevice = await navigator.bluetooth.requestDevice(
			this.bluetoothDeviceSearchOptions
		);
		this.bluetoothDevice$.next(userSelectedSchwinIc4Bike);

		const serverConnection:
			| BluetoothRemoteGATTServer
			| undefined = await userSelectedSchwinIc4Bike?.gatt?.connect();
		this.bluetoothServer$.next(serverConnection);

		return (await serverConnection?.getPrimaryServices()) ?? [];
	};

    /**
      * !!!! this portion is where I'm having trouble after i'm connected and receiving responses. !!!!!
      **/
	private readonly parseCadenceWheelSpeedWheelTime = event => {
        // here is where the value of the notification would be something like
        // 0x593400e04890
        // this buffer is embedded in a DataView object.
		const { value } = event?.target;
		const dataView = value as DataView;
		console.log(`some real data: ${dataView.getUint16(0, true)}`);
		console.log(`some other real data: ${dataView.getUint16(2, true)}`);
	};
}

 

thing.PNG

Edited by super_teabag
Link to comment
Share on other sites

Link to post
Share on other sites

Can't say I'm brushed up on this but looked quite interesting, did a little digging and saw this example: https://developer.mozilla.org/en-US/docs/Web/API/BluetoothCharacteristicProperties

If I understand correctly you're just trying to read the data from the cscMeasurement? The above implies that the `event?.target` has a 'value' (Not a DataView?). I came across this as well: https://googlechrome.github.io/samples/web-bluetooth/discover-services-and-characteristics.html?optionalServices=link_loss Might help you debug ~ though that just seems to show the properties of a given characteristic - probably easy to modify.

Link to comment
Share on other sites

Link to post
Share on other sites

@AnotherMaxthese are a good starting point, but I found some more resources that were more useful that are super buried on the Bluetooth documentation's website. I'll post here just in case you're curious. Bluetooth Web Tutorial from the horses mouth (it's still dated)

 

Second, the "characteristics" in Bluetooth terminology are properties in a grouping. that grouping is called a "service" (this isn't me trying to be an asshole about terms). So, the "value" is typed as DataView since the standard has been updated and bugs have been fixed in Chrome / Edge. Again, making the "value" property as a DataView. You'll have to checkout mozilla's documentation on that if you'd like.

 

Effectively I think I'm SOL cuz it's up to the manufacturer of the device to adhere to the standard for say "heart rate" services. then provide a way to parse the data.

I've shot nautilus (parent company) a message about what I'm dealing with. Hopefully, they'll give me a data sheet with some more information.

 

I can parse some of the values from the HEX information provided, but that's just it. I don't know if that's 100% where the data is.

 

 

Link to comment
Share on other sites

Link to post
Share on other sites

Yeah that's sounds tricky, sorry I couldn't help out more!

Maybe a naive appoarch, but couldn't you brute force it? e.g. the DataView will have a limited amout of bytes and I suppose you could observe the changing value.

Link to comment
Share on other sites

Link to post
Share on other sites

@AnotherMax It's cool duder. yeah, so... that's my last straw that I'm gonna try. There's a few portions of the data MSB (most significant bit) that keeps changing when changing what device it's paired too. Otherwise, I'm 90% certain that bytes 0-2 and 2-4 are for timing cuz they're unsigned 16 bit integers and they line up.

If I don't have to brute force it and can ask. That's what I'm doing rn cuz work smarter not harder

Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×