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!
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.
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.
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.
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.
With this, now it’s up to us to make the best out of the combination of Raspberry Pi and Surface Dial.
- How to Setup Bluetooth on a Raspberry Pi 3 – CNET;
- bluetooth – List of bluetoothctl : paired-devices is empty after a reboot – Raspberry Pi Stack Exchange;
- rickhull/device_input: Read Linux Kernel Device Input Events from Ruby;
- Working with /dev/input/eventX from Go;
- Tutorial — Python-evdev;
- Linux & Dotnet – Read from a device file – HoNoSoFt;
- Linux Input drivers v1.0 (c) 1999-2001 Vojtech Pavlik.