Summer had arrived and I ran into some true first world problems. Sitting outside with my laptop and listening to Spotify on my headphones is a pleasurable way to spend a sunny day, but I still got some things to handle here and there inside my place. And hooray for technology, I can transfer my playback from my laptop to my desktop inside and can almost seamlessly continue listening to whatever I'm listening to.

However, that involves a manual switching of my playback devices before I leave to the inside / when I come back outside, which is sometimes way too easy to forget to do that, and I always had to make a detour to do the switch-over then. I did promise first world problems, didn't I?

Having a history with Bluetooth beacons and indoor positioning using Ultra-wide band beacons, I figured this could be a valid use case for my issues. Since I'm usually carrying my phone with me anyway, I could have an app that monitors the location of some beacons, figuring out where about I am at the moment, and automatically transfer the playback between the devices via the Spotify Web API.

While Bluetooth beacons surely have their limits for real positioning, they do a well enough job for a rough approximation which of two points are closer. Plus, if your phone can do Bluetooth LE, it can detect beacons out of the box. So all I needed was some beacons, but since that's in the end just regular BLE advertising data in a specific format, you can easily set up any BLE capable device as one. In my case, it's my laptop and some random Raspberry Pi that do the job.

Setting up the beacons

I've chosen then AltBeacon protocol for the ranging, which comes with a handy shell script to set up the single fields in the advertisement data and make your BLE device advertise it using BlueZ. There's also a slightly adjusted version in the SpotifindMe git repository based on that same script.

The most important part is to set up the Beacon ID and Reference RSSI advertisement fields properly. The app is expecting two beacons with Beacon Unit ID (ID3) of 0x0001 and 0x0002. The script itself is set up with id 0x0001 already, so you only need to modify it for a second device that will act as beacon:

AD_Data_ID3="00 02"    # Beacon Unit as 2-byte value 

To adjust the reference RSSI value, you should measure the RSSI value of your beacon in one meter distance (there should be an app for that?), which will give you a negative value, let's say -54. The reference RSSI variable needs to set as a signed one-byte integer, so we need to give the two's complement of the measured value.

$ printf "%2x\n" $((-54 & 0xff))
ca
$

So if we have -54, we need to set 0xca in the script.

AD_Data_Reference_RSSI="ca"

That's it, ready to go, just run the script, it will output what's going on and set up your BLE device through hciconfig and hcitool, and returns back to the terminal. The rest is done by the BLE device itself.

$ ./tools/altbeacon_transmit.sh
Transmitting AltBeacon profile with the following identifiers:
ID1: 53 70 6f 74 69 66 69 6e 64 4d 65 00 00 00 00 00
ID2: 00 01
ID3: 00 02
MFG RESERVED: 01

AltBeacon Advertisement: 1b ff 18 01 be ac 53 70 6f 74 69 66 69 6e 64 4d 65 00 00 00 00 00 00 01 00 02 ca 01

LE set advertise enable on hci0 returned status 12
< HCI Command: ogf 0x08, ocf 0x0008, plen 32
  1F 02 01 1A 1B FF 18 01 BE AC 53 70 6F 74 69 66 69 6E 64 4D 
  65 00 00 00 00 00 00 01 00 02 CA 01 
> HCI Event: 0x0e plen 4
  01 08 20 00 
$

There we go.

 Android app

The SpotifindMe app was written with Android Studio, and can be simply imported there.

The app handles a couple of things:

To make it work with your own devices, you'll have to adjust the myDevices array in MainActivity.java to match your own devices. The names are case sensitive, so take care that matches. Or then replace the equals() functions later in the code.

    private static final String[] myDevices = new String[] {
            "Laptop", // change to one of your Spotify device's name
            "Desktop" // change to another Spotify device's name
    };

If you used some other Beacon Unit IDs than 0x0001 and 0x0002, you'll have to adjust the myBeacons array as well.

    private static final int[] myBeacons = new int[] {
            1,
            2
    };

You might also want to have a look at the checkSituation() method in MainActivity.java and adjust the beacon distances accordingly. They're definitely more trial and error values than anything scientific, but that's the Bluetooth Beacon life.

    private void checkSituation() {

        ...

        if (d1 > 2.0 && d2 < 5.0 && currentDevice.equals(myDevices[0])) {
            transferPlayback(myDevices[1]);
        } else if (d1 < 1.5 && currentDevice.equals(myDevices[1])) {
            transferPlayback(myDevices[0]);
        }
    }

This is also the spot where to change the equals() calls mentioned earlier. 

Note that the app won't work without Bluetooth enabled, or if Bluetooth isn't available, as it is the case with the built-in Android Emulator. It will also ask you to grant permissions to access the location, Bluetooth also won't work without that.

Register as your own Spotify application

To get access to the Spotify Web API, the application requesting the data needs to be registered within Spotify's own developer portal. The app also needs to be signed and the fingerprint set in your Spotify developer dashboard as well. If you build the app on your own machine, it will use your own debug fingerprint to sign it, which of course differs from mine. As a result, you cannot use the app out of the box, but have to register it for yourself.

You mainly just have to sign to the developer portal with your Spotify account (which you most likely will have, otherwise there's not much point in this app anyway) and register an application. Here are some more instructions.

Once registered, you'll get your own client ID that you have to set in LoginActivity.java and either set your own redirect URI in the Spotify dashboard to the one in REDIRECT_URI, or set your own and change the constant accordingly.

    /** SpotifindMe client ID, used to identify the application with Spotify itself */
    private static final String CLIENT_ID = "...";
    /** Spotify auth redirect URL */
    private static final String REDIRECT_URI = "spotifindme://redirect";

  What's next?

Honestly, not much. It was a semi serious idea, and a proof of concept implementation proved its concept. While not the smoothest transitions between the devices (we're talking few seconds here ... ), it's good enough consider the simple distance checking logic.

I might consider adding an automatic auth token refresh when needed, since they expire after an hour, and I may not always move that often, so I always need to open the app and click a button and oh my god. ..yes.

Sure, one could add some dynamic configuration options, let you set your device names, beacon information, and switching rules at runtime instead. But at this point, I don't think there is a use for that. Yet?