A photo of a Raspberry Pi 2 with Polar H7 heart rate monitor.

In addition to your Raspberry Pi, you will need the following hardware:

Next, download, compile and install the latest ‘BlueZ’ on your Raspberry Pi. BlueZ is the Bluetooth stack for Linux, and it works with Raspbian. Plug your bluetooth dongle into your Pi and the following commands will install BlueZ:

$ wget http://www.kernel.org/pub/linux/bluetooth/bluez-5.37.tar.xz
$ tar -xvf bluez-5.37.tar
$ ./configure --disable-systemd
$ make
$ sudo make install

The peripheral ID of the heart rate monitor is the address we use for connection. To get the peripheral ID of your Polar H7 heart rate monitor:

$ sudo hciconfig hci0 up
$ sudo hcitool -i hci0 lescan

This will return some output like:

LE Scan...
00:D2:D0:94:C2:C0 (unknown)
00:D2:D0:94:C2:C0 Polar H7 97C4C011

Use control-c to stop the scan. We are just interested in the line with ‘Polar H7’ in the description. In the example above, our peripheral ID is 00:D2:D0:94:C2:C0.

The programming language Go and the gatt library from Paypal can connect and retrieve data from the heart rate monitor. Before running a gatt program, make sure that the BLE device is down (off):

$ sudo hciconfig hci0 down
$ sudo service bluetooth stop

The gatt library defines callbacks for different stages of the peripheral connection process. It has a callback for when your bluetooth dongle changes state (like when it gets powered on). You can also set callbacks when a peripheral is discovered, connected and disconnected. So when we power up the bluetooth device, we start scanning for the peripheral ID of the Polar H7. When found, connect and when we have finished, clean up and disconnect.

d, err := gatt.NewDevice(option.DefaultClientOptions...)
if err != nil {
	log.Printf("ERROR: Unable to get bluetooth device.")
	return
}

// Register handlers.
d.Handle(
	gatt.PeripheralDiscovered(func(p gatt.Peripheral, a *gatt.Advertisement, rssi int) {
		onPeriphDiscovered(p, a, rssi, deviceID)
	}),
	gatt.PeripheralConnected(func(p gatt.Peripheral, err error) {
		onPeriphConnected(p, done, err)
	}),
	gatt.PeripheralDisconnected(func(p gatt.Peripheral, err error) {
		onPeriphDisconnected(p, done, err)
	}),
)

d.Init(onStateChanged)

Reading information from the Heart Rate monitor all happens in the connected callback. The Bluetooth GATT specification calls attributes with a single logical value a ‘Characteristic’. Like Characteristics get grouped together into ‘Services’. The Polar H7 supports the Heart Rate, Device Information and Battery services.

The heart rate measured by the Polar H7 in beats per minute is tucked away in the ‘Heart Rate Measurement’ characteristic of the ‘Heart Rate’ service.

// Get the heart rate service which is identified by the UUID: \x180d
ss, err := p.DiscoverServices([]gatt.UUID{gatt.MustParseUUID("180d")})
if err != nil {
	log.Printf("ERROR: Failed to discover services - %s\n", err)
	return
}

for _, s := range ss {
	// Get the heart rate measurement characteristic which is identified by the UUID: \x2a37
	cs, err := p.DiscoverCharacteristics([]gatt.UUID{gatt.MustParseUUID("2a37")}, s)
	if err != nil {
		log.Printf("ERROR: Failed to discover characteristics - %s\n", err)
		continue
	}

	for _, c := range cs {
		// Read the characteristic.
		if (c.Properties() & gatt.CharRead) != 0 {
			_, err := p.ReadCharacteristic(c)
			if err != nil {
				log.Printf("ERROR: Failed to read characteristic - %s\n", err)
				continue
			}
		}

		// Discover the characteristic descriptors.
		_, err := p.DiscoverDescriptors(nil, c)
		if err != nil {
			log.Printf("ERROR: Failed to discover descriptors - %s\n", err)
			continue
		}

		// Subscribe to any notifications from the characteristic.
		if (c.Properties() & (gatt.CharNotify | gatt.CharIndicate)) != 0 {

			err := p.SetNotifyValue(c, func(c *gatt.Characteristic, b []byte, err error) {
				heartRate := binary.LittleEndian.Uint16(append([]byte(b[1:2]), []byte{0}...))
				contact := binary.LittleEndian.Uint16(append([]byte(b[0:1]), []byte{0, 0}...))

				// Notify if the HRM has skin contact, and the current measured Heart rate.
				if contact == 6 || contact == 22 {
					fmt.Printf("1,%d\n", heartRate)
				} else {
					fmt.Printf("0,%d\n", heartRate)
				}
			})

			if err != nil {
				log.Printf("ERROR: Failed to subscribe characteristic - %s\n", err)
				continue
			}
		}

	}
	log.Println()
}

Full source code for reading from a Polar H7 and outputting the result to stdout with Go can be found here.

References

* You can use any heart rate monitor that supports the Bluetooth Heart Rate GATT Profile.