Controlling Raspberry Pi with Surface Dial

Last week, I got a Surface Dial from Riza Marhaban because the device looks cool.

Microsoft always produces cool products such as the Zune music player, Windows Mobile, Nokia Lumia phone, etc. However, most of them are discontinued and are now difficult to be found in the market. Even though I hope that Surface Dial won’t have a similar fate as them, I still decide to get one before anything happen to it.

If you are not sure what Surface Dial is, previously, I had shared in another blog post about how we could use Surface Dial in our UWP applications on Windows 10. I have also found a video, which I attach it below, about how people do amazing music with Surface Dial on Windows.

Using Surface Dial Outside of Windows

Many exciting examples online seem to show that we can only use Surface Dial on Windows. Well, that is not true.

Starting from Linux 4.19 released on 22nd October 2018, Surface Dial is supported. Fortunately, if we are using the standard Raspberry Pi OS update/upgrade process, the Linux kernel we have in our Raspberry Pi should be the latest stable version. So that means we can connect Surface Dial to, for example, our Raspberry Pi 3 Model B too.

I use Raspberry Pi 3 Model B as example here because that is the only one that I have now and I have previously set it up with the latest Raspberry Pi OS. Another reason of using Raspberry Pi 3 Model B is because it comes with built-in Bluetooth 4.1.

Pair and Connect Surface Dial to Raspberry Pi

After we SSH into the Raspberry Pi, we will use the bluetoothctl command to pair the Raspberry Pi with a bluetooth device which is a Surface Dial in this case.

$ sudo bluetoothctl

On a smart phone, for example, when we are pairing the phone with a Bluetooth device, we always need to go through an authentication where we will be prompted for a password or asked whether we would like to connect to the Bluetooth device. So how is that being done using bluetoothctl?

Within the bluetoothctl, we need to first register something called Agent. Agent is what manages the Bluetooth “pairing code” and we set the Agent to be the Default Agent.

[bluetooth]# agent on
Agent registered
[bluetooth]# default-agent 
Default agent request successful

Next, we will scan for the nearby Bluetooth devices with the following command. Since the Surface Dial is made by default discoverable, the device name “Surface Dial” is visible together with its Bluetooth address.

[bluetooth]# scan on
Discovery started
[NEW] Device XX:XX:XX:XX:XX:XX Surface Dial

Now we can then pair and connect to the Surface Dial using its Bluetooth address.

[bluetooth]# pair XX:XX:XX:XX:XX:XX
...
[bluetooth]# connect XX:XX:XX:XX:XX:XX
Attempting to connect to XX:XX:XX:XX:XX:XX
[CHG] Device XX:XX:XX:XX:XX:XX Connected: yes 

Now we have Surface Dial successfully connected to our Raspberry Pi. Yay!

Exploring /dev/input

The /dev directory contains all the device files for all the devices connected to our Raspberry Pi. Then we have an input subdirectory in /dev which holds the files for various input devices such as mouse and keyboard or in our case, the Surface Dial.

[Image Caption: The content of /dev/input on my Raspberry Pi.]

With so many event files, how do we know which events are for which input devices? To find that out, we just need to use the following command.

$ cat /proc/bus/input/devices

Then we will see something as follows. The following is the output on my Raspberry Pi.

 I: Bus=0003 Vendor=0d8c Product=000c Version=0100 
 N: Name="C-Media USB Headphone Set  "
 ...
 H: Handlers=kbd event0
 ...

 I: Bus=0005 Vendor=045e Product=091b Version=0108
 N: Name="Surface Dial System Multi Axis"
 ...
 H: Handlers=event1 
 ...
  
 I: Bus=0005 Vendor=045e Product=091b Version=0108
 N: Name="Surface Dial System Control"
 ...
 H: Handlers=kbd event2  
 ...

So based on the Handlers, we know that event1 and event2 bind to the Surface Dial.

Input Event Messages

The event binary messages are C structs, we need to be able to delimit individual messages and decode them. Unless you are, for example, using the beta test of the Raspberry Pi OS, normally we are recommended to use 32-bit OS for Raspberry Pi. On 32-bit platforms, the event messages have 16 bytes each. On 64-bit platforms, the event timestamp is 16 bytes and thus each event message has 24 bytes each.

[Image Caption: Event message size in 32-bit and 64-bit platforms.]

Reading Surface Dial Input Events with Golang

There are three basic actions we can do on Surface Dial, i.e. turn left, turn right, and click. Now we know that the events generated by these actions from Surface Dial can be found in the event1, so what we need to do is just to find a way to read device input events from /dev/input/event1.

There are many ways of doing that. There are online documentations on how to do it in Python with evdev and C# on .NET Core. I didn’t try them out. Instead what I try is to write a simple Golang application to process the event messages. In 2017, Janczer had successfully read and parsed the event messages with Golang.

However, Janczer was working on a 64-bit platform so today I’m going to share how to change the code to work on a Raspberry Pi running on a 32-bit OS.

package main
import (
"bytes"
"encoding/binary"
"fmt"
"os"
"time"
)
func main() {
f, err := os.Open("/dev/input/event1")
if err != nil {
panic(err)
}
defer f.Close()
b := make([]byte, 16)
for {
f.Read(b)
fmt.Printf("%b\n", b)
sec := binary.LittleEndian.Uint32(b[0:8])
t := time.Unix(int64(sec), 0)
fmt.Println(t)
var value int32
typ := binary.LittleEndian.Uint16(b[8:10])
code := binary.LittleEndian.Uint16(b[10:12])
binary.Read(bytes.NewReader(b[12:]), binary.LittleEndian, &value)
fmt.Printf("type: %x\ncode: %d\nvalue: %d\n", typ, code, value)
}
}
view raw event_message.go hosted with ❤ by GitHub

Now, when I turn or press on the Surface Dial, I will be able to see the output as shown in the screenshot below. I run the program three times so that I can easily demonstrate the outputs for turning Surface Dial clockwise, turning it counter-clockwise, and clicking it, respectively.

[Image Caption: Event messages sent from the Surface Dial to Raspberry Pi.]

With this, now it’s up to us to make the best out of the combination of Raspberry Pi and Surface Dial.

References

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s