diary at Telent Netowrks

Listening to a Polar Bluetooth HRM in Linux#

Thu, 03 May 2012 22:10:29 +0000

My new toy, as of last Friday, is a Polar WearLink®+ transmitter with Bluetooth® because I wanted to track my heart rate from Android. Absent some initial glitches which turned out to be due to the battery it was shipped with having almost no charge left, it works pretty well with the open source Google My Tracks application.

But, but. A significant part of my exercise regime consists of riding a stationary bicycle until I feel sick. I do this in the same room as my computer: not only are GPS traces rather uninformative for this activity, but getting satellite coverage in the first place is tricky while indoors. So I thought it would be potentially useful and at least slightly interesting to work out how to access it directly from my desktop.

My first port of call was the source code for My Tracks. Digging into src/com/google/android/apps/mytracks/services/sensors/PolarMessageParser.java we find a helpful comment revealing that, notwithstanding Polar's ridiculous stance on giving out development info (they don't, is the summary) the Wearlink packet format is actually quite simple.

 *  Polar Bluetooth Wearlink packet example;
 *   Hdr Len Chk Seq Status HeartRate RRInterval_16-bits
 *    FE  08  F7  06   F1      48          03 64
 *   where; 
 *      Hdr always = 254 (0xFE), 
 *      Chk = 255 - Len
 *      Seq range 0 to 15
 *      Status = Upper nibble may be battery voltage
 *               bit 0 is Beat Detection flag.

While we're looking at Android for clues, we also find the very useful information in the API docs for BluetoothSocket that "The most common type of Bluetooth socket is RFCOMM, which is the type supported by the Android APIs. RFCOMM is a connection-oriented, streaming transport over Bluetooth. It is also known as the Serial Port Profile (SPP)". So, all we need to do is figure out how to do the same in Linux

Doing anything with Bluetooth in Linux inevitably turns into an exercise in yak epilation, especially for the kind of retrocomputing grouch (that's me) who doesn't have a full GNOME or KDE desktop with all the D buses and applets and stuff that come with it. In this case, I found that XFCE and the Debian blueman package were sufficient to get my bluetooth dongle registered, and to find and pair with the HRM. It also included a natty wizard thing which claimed to be able to create an rfcomm connection in /dev/rfcomm0. I say "claimed" not because it didn't - it did, so ... - but because for no readily apparent reason I could never get more than a single packet from this device without disconnecting, unpairing and repairing. Perhaps there was weird flow control stuff going on or perhaps it was something else, I don't know, but in any case this is not ideal at 180bpm.

So, time for an alternative approach: thanks to Albert Huang, we find that apparently you can work with rfcomm sockets using actual, y'know, sockets . The rfcomm-client.c example on that we page worked perfectly, modulo the obvious point that sending data to a heart rate monitor strap is a peculiarly pointless endeavour, but really we want to write our code in Ruby not in C. This turns out to be easier than we might expect. Ruby's socket library wraps the C socket interface sufficently closely that we can use pack to forge sockaddr structures for any protocol the kernel supports, if we know the layout in memory and the values of the constants.

How do we find "the layout in memory and the values of the constants"? With gdb. First we start it

:; gdb rfcomm-client
[...]
(gdb) break 21
Breakpoint 1 at 0x804865e: file rfcomm-client.c, line 21.
(gdb) run
Starting program: /home/dan/rfcomm-client

Breakpoint 1, main (argc=1, argv=0xbffff954) at rfcomm-client.c:22 22 status = connect(s, (struct sockaddr *)&addr, sizeof(addr));

then we check the values of the things

(gdb) print sizeof addr
$2 = 10
(gdb) print addr.rc_family 
$3 = 31
(gdb) p/x addr.rc_bdaddr
$4 = {b = {0xab, 0x89, 0x67, 0x45, 0x23, 0x1}}

then we look at the offsets

(gdb) p/x &addr
$5 = 0xbffff88e
(gdb) p/x &(addr.rc_family)
$6 = 0xbffff88e
(gdb) p/x &(addr.rc_bdaddr)
$7 = 0xbffff890
(gdb) p/x &(addr.rc_channel)
$8 = 0xbffff896

So, overall length 10, rcfamily is at offset 0, rcbdaddr at 2, and rc_channel at 8. And the undocumented (as far as I can see) str2ba function results in the octets of the bluetooth address going right-to-left into memory locations, so that should be easy to replicate in Ruby.

  def connect_bt address_str,channel=1
    bytes=address_str.split(/:/).map {|x| x.to_i(16) }
    s=Socket.new(AF_BLUETOOTH, :STREAM, BTPROTO_RFCOMM)
    sockaddr=[AF_BLUETOOTH,0, *bytes.reverse, channel,0 ].pack("C*")
    s.connect(sockaddr)    
    s
  end

The only thing left to do is the actual decoding. Considerations here are that we need to deal with short reads and that the start of a packet may not be at the start of the buffer we get - so we keep reading buffers and catenating them until decode says it's found a packet, then we start again from where decode says the end of the packet should be. Because this logic is slightly complicated we wrap it in an Enumerator so that our caller gets one packet only each and every time they call Enumerator#next

The complete example code is at https://gist.github.com/2500413 and the licence is "do what you like with it". What I will like to do with it is (1) log the data, (2) put up a window in the middle of the display showing my instantaneous heart rate and zone so that I know if I'm trying, (3) later, do some interesting graphing and analysis stuff. But your mileage may vary.

Changing sort order with ActiveRecord find_in_batches#

Fri, 04 May 2012 07:29:34 +0000

The subject is Googlebait pure and simple, because the short version is that you can't. The very slightly longer and significantly more defensible version is that you may be able to but they don't want you to apparently because you might get the wrong answer if you mutate the data as you're traversing it (and because it's fast in MySql).

Personally I think the answer there is Well Don't Do That Then (and who cares about MySql) but that's just my opinion. If you want to order by, say, created_at descending, and perhaps you want to paginate the results, the only sensible conclusion to draw is that find_in_batches is just not intended for this use.

But it's not an unreasonable use. So I wrote ar-as-batches which lets you do

Users.where(country_id: 44).order(:joined_at).offset(200).as_batches do |user|
  user.party_all_night!
end

and it all works as you'd expect. I should of course caution you that

I don't know whether to be proud or ashamed of the tests , which check the generated queries by assigning a StringIO logger to ActiveRecord::Base.logger and then matching regexps in it after each test runs. There ought to be a better way. Perhaps there is a better way. Don't know what though.

Debian on the Samsung Series 9 NP900X3B#

Mon, 28 May 2012 11:40:28 +0000

There are other guides to getting Linux going on the Samsung Series 9 NP900X3B, but both that I've found are for Fedora. Mostly it's the same in Debian, but here are some of the things I've spotted.

Install media

Recent versions of Debian install media images are created as hybrid ISO images, which means you can download them and dd them directly to a USB stick. This is what I did, with the Squeeze netinst image. The computer's BIOS settings needed changing to look at USB before the internal SSD, but that's not hard. I deselected all packages which resulted in a very minimal basic install, then added xfce4 and a few essential utilities using apt-get

Networking

The wired network works out of the box.

The wireless networking is courtesy of an Intel 6230 adapter "Intel Corporation Centrino Advanced-N 6230 [8086:0091] (rev 34)" (apparently this also does Bluetooth, but I haven't tried that yet). This is not supported in Squeeze's default kernel, but is available in Wheezy. After much swearing at backports I decided to do the apt-get upgrade dance

Touchpad

Touchpad handling worked in Squeeze and partially broke when I upgraded to get working wifi. Pointer movement worked fine, but tapping (for the uninitiated, "tapping" on the touchpad is simulating button presses by briefly touching the pressure-sensitive area instead of the hardware buttons below it) didn't. On this system tapping is infinitely preferable to the hardware buttons, because it appears impossible to move the pointer while one of the hardware buttons is pressed - this makes window placement pretty tricky. The fix here is

synclient TapButton1=1 
synclient TapButton2=2 
synclient TapButton2=3 
synclient PalmDetect=1

which means you can use one finger to simulate button 1, two fingers simultaneously (note: not double-clicking, as I foolishly initially thought) to simulate button 2, and three for button 3. It also turns on palm detection, so that accidentally brushing the pad as you type won't send your cursor off into the wild blue yonder.

This affect the current session only. To make it permanent you need to edit files: copy /usr/share/X11/xorg.conf.d/50-synaptics.conf to /etc/X11/xorg.conf.d and add the lines

        Option "TapButton1" "1"
        Option "TapButton2" "2"
        Option "TapButton3" "3"
        Option "PalmDetect" "1"

in the first Section "InputClass" stanza

Suspend and hibernate

I had the same problem with suspend as John Teslade : it appears not to resume but in fact it works perfectly except for the display backlight. However, I had it harder because my Fn-F2 and Fn-F3 keys didn't do anything. After determining with acpi_listen that Linux is listening to those keys (they send ACPI events video/brightnessdown and video/brightnessup respectively) and is capable of controlling the brightness (try e.g. xrandr --output eDP1 --set BACKLIGHT 1) I decided this must clearly be a 90% solved problem and that the missing link was probably somewhere in the Debian package archive. It was, it was xfce4-power-manager

After that, suspend and hibernate are both usable.

Battery life

Pretty poor right now (looks like about 3 hours), but I've just installed laptop-mode-tools which has turned most of the PowerTOP tunables from "Bad" to "Good", so I will be disappointed if that doesn't make a significant difference. We'll see ...