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)
}
}

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

[KOSD Series] When Surface Dial meets UWP

Starting from end of last year, I have been working on several UWP projects at work. Few days ago, I’m glad to have the opportunity to borrow a Surface Dial from Riza.

Surface Dial is a revolutionary input device. It was introduced by Microsoft in the 2016 together with Surface Studio. Most of the time, it works awesome on Surface devices. However, that doesn’t mean we can’t use it with our Windows desktop or laptop. As a secondary input device, it can be used together with our mouse or touch. Developers are also welcomed to customize the abilities of the Surface Dial on their UWP apps.

In this article, I will be sharing on how we can use Surface Dial to support a quick menu of commands in an UWP app.

Installing Surface Dial

First of all, we need to make sure our Windows device is on (Yes, UWP currently can only work on Windows machines). Then we need to turn on the Bluetooth on the machine. If our PC doesn’t come with the Bluetooth hardware installed, we can easily add it by purchasing a Bluetooth USB dongle.

After that, we need to turn our Surface Dial on by removing the cover of its bottom and then pressing the button next to the batteries, as shown in the photo below.

🎨 Added Surface Dial to my PC. 🎨

Now we can find the Surface Dial on the “Bluetooth & other devices” window. We can proceed to add it to our PC.

Adding Menu on Surface Dial

For those who attended my sharing in Microsoft Insider Dev Tour 2019 in Kuala Lumpur and Johor Bahru last year, the following UWP app should be familiar to you.

🎨 The UWP demo app done by Justin Liu for Microsoft Insider Dev Tour. 🎨

Now we will proceed to add Surface Dial menu to this page.

In the code-behind of the page, we will have the following global objects.

private RadialController radialController;
private RadialControllerConfiguration radialControllerConfig;

Then, we can initialize the RadialController.

// Create a reference to the RadialController.
radialController = RadialController.CreateForCurrentView();
// Set rotation resolution to 1 degree of sensitivity.
radialController.RotationResolutionInDegrees = 1;

What does setting RotationResolutionInDegrees mean here? The value actually is the minimum rotation value required for the RotationChanged event to be fired. So, by setting it to 1, every one degree of rotate on the Surface Dial, the RotationChanged event will be triggered. Also, by default, when the RotationChanged happens, the Surface Dial will vibrate. So it is like massaging your hand when you’re rotating the Surface Dial that has its RotationResolutionInDegrees set to 1.

Then we can proceed to add our menu items to the Surface Dial. Here, we use a font glyph for the custom tool.

var imageGallery = RadialControllerMenuItem.CreateFromFontGlyph("Image Gallery", "\xE15A", "Segoe MDL2 Assets");
...
radialController.Menu.Items.Add(imageGallery);

However, please take note that, by default, there are built-in menu items for the Surface Dial. So we need to remove them to prevent squeezing in too much menu items to the Surface Dial UI and thus making it harder to control.

To remove the built-in menu items, we just need to reset in the configuration of the Surface Dial. Another thing to take note is that the Surface Dial menu must have at least one menu item, else the default menu items will be restored.

radialControllerConfig = RadialControllerConfiguration.GetForCurrentView();

radialControllerConfig.SetDefaultMenuItems(new RadialControllerSystemMenuItemKind[] { });

Now there is a funny thing is that if we remove all the built-in menu items before we add our customized menu items, i.e swapping the position of the two blocks of codes above, then we will realize that the default menu items will be restored and our customized menu items will be appended to the default ones, as shown in the screenshot below.

🎨 Oh my tian, the buttons on the Surface Dial menu are so crowded! 🎨

Finally, if we want to handle the events fired from the Surface Dial, for example when users click on it or rotate it, we can use the following handlers.

radialController.ButtonClicked += RadialController_ButtonClicked;
radialController.RotationChanged += RadialController_RotationChanged;

Please take note that the ButtonClicked event is not triggered when a menu item is selected. Instead we need to do as follows to handle the menu item chosen event.

imageGallery.Invoked += ImageGallery_Invoked;

Result

So, now, with all these few lines of codes, we can have a beautiful Surface Dial control on our UWP app, as shown in the following photo. Yay!

🎨 Control the universe with our hand. 🎨

References

  1. Support the Surface Dial (and other wheel devices) in your UWP app;
  2. Creating Custom Dial Menu.

KOSD, or Kopi-O Siew Dai, is a type of Singapore coffee that I enjoy. It is basically a cup of coffee with a little bit of sugar. This series is meant to blog about technical knowledge that I gained while having a small cup of Kopi-O Siew Dai.