Microsoft Surface Dial or Other Physical BlueTooth Control

I think that requires actually connecting a physical dial to the GPIO pins on the Pi (which I could probably figure out) and then a bunch LINUX code magic, which I cannot :slight_smile:

I tried the PlugIn with the MS Surface Dial connected via BT but no joy.

1 Like

Correct… a rotary encoder and the relevent componants connected to the GPIO pins.

No none of that is needed, all you have to do is configure the rotary encoder II plugin with the correct details.

Not 100% sure what you mean by this, but I have a external volume control that sits on the arm of my chair with a 1.2 meter cable running to my Volumio box with zero issues

1 Like

So hopefully I ordered the correct components needed:

Yeah not a big fan of these rotary encoders, I ended up using a BT remote.

import asyncio
import struct
import time
import subprocess
import os

from evdev import InputDevice, categorize, ecodes

EV_VAL_PRESSED = 1
EV_VAL_RELEASED = 0
# mousea:
KEY_ENTER =      28
KEY_LEFT =       105
KEY_RIGHT =      106
KEY_UP =         103
KEY_DOWN =       108
KEY_COMPOSE =    127
# mouseb:
KEY_HOMEPAGE =   172
KEY_BACK =       158
KEY_VOLUMEDOWN = 114
KEY_VOLUMEUP =   115

mousea = InputDevice('/dev/input/event0')
mouseb = InputDevice('/dev/input/event1')
mousec = InputDevice('/dev/input/event2')
moused = InputDevice('/dev/input/event3')

print(mousea)
print(mouseb)
print(mousec)
print(moused)
print('=== Start ===')

async def print_events(device):
    global  STATUS            
    async for event in device.async_read_loop():
        if event.type == ecodes.EV_KEY:
            if event.value == EV_VAL_PRESSED:
                if event.value == EV_VAL_PRESSED:
                    if event.code == KEY_ENTER:
                        print('<ENTER> Pressed')
                        command = 'volumio toggle'
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)                        
                    elif event.code == KEY_LEFT:
                        print('<LEFT> Pressed')
                        command = 'volumio previous'
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)                         
                    elif event.code == KEY_RIGHT:
                        print('<RIGHT> Pressed')
                        command = 'volumio next'
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)                         
                    elif event.code == KEY_UP:
                        print('<UP> Pressed')
                        command = 'volumio seek plus'
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)                         
                    elif event.code == KEY_DOWN:
                        print('<DOWN> Pressed')
                        command = 'volumio seek minus'
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)     
                    elif event.code == KEY_COMPOSE:
                        print('<COMPOSE> Pressed')
                        command = 'node /data/plugins/user_interface/randomizer/randomTracks'
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE) 
                    elif event.code == KEY_BACK:
                        print('<BACK> Pressed')
                        command = 'node /data/plugins/user_interface/randomizer/randomAlbum'                        
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE) 
                    # elif event.code == KEY_HOMEPAGE:
                        # print('<HOME> Pressed')

                    elif event.code == KEY_VOLUMEDOWN:
                        print('<VOLUMEDOWN> Pressed')
                        command = 'volumio volume minus'
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)      
                        
                    elif event.code == KEY_VOLUMEUP:
                        print('<VOLUMEUP> Pressed') 
                        command = 'volumio volume plus'
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE) 
           
                print(device.path, categorize(event), sep=': ')          
                print('---')

for device in mousea, mouseb, mousec, moused:
    asyncio.ensure_future(print_events(device))

loop = asyncio.get_event_loop()
loop.run_forever()

The 1/2 watt resistors are a little excessive but they will do.
And ideally a 100nf capacitor between ground and each of the gpio’s. Just to make the action acurate and stable.

If you are also using the button on the rotary bear in mind there is no pull up resistor on the CYT1062’s for the switch.

That’s EXACTLY what I wanted to do. I got a Microsoft Surface Dial, got it connected to the device but couldn’t figure out where to go from there. Would what you have above work or does it need to be customized for each device?

Not the buttons, but yo need to find the events belonging to your dial.
All available keys can be found here:

Look at /dev/input/ and check the events while you press every combination of the thingy.
like:

cat /dev/input/event0
cat /dev/input/event1
cat /dev/input/event2
cat /dev/input/eventx

And add these events belonging to your thingy to the script.

Thanks… that level of code awareness is beyond me - I’m hardware guy with great Photo editing skills and zero coding ability. If I could hire someone to write it for me - that’s another skill :slight_smile:

All you need to do is finding the events belonging to the dail.
And update the event numbers:

mousea = InputDevice('/dev/input/event0')
mouseb = InputDevice('/dev/input/event1')
mousec = InputDevice('/dev/input/event2')
moused = InputDevice('/dev/input/event3')

Since this depends on the USB port, what else is connected to the USB, you need to do this by yourself.

USB 2.0 ports going to 5.5 AMOLED touch screen and SMSL DAc
USB 3.0 ports going to external WiFi and External SSD

Does that matter since the Dial is BlueTooth?

please read:

You’re going to make me code, aren’t you? :wink:

I already gave you the complete code all you have to do is get the proper events as instructed.
I get the impression your better in asking questions than in reading?

I literally have no idea what do with the code or how to “get” the events. I’ve read through it but it’s just a wall of text that assumes you understand the coding and instruction set.

Sorry to be a bother and you can go ahead and consider this closed - this has skill requirements above what I have or am willing to invest the time to learn so I’ll just another option for volume control.

I gave t-you the complete code, instructions what you need to do.
Since I don’t have the hardware, you do, this is on you to determine.
ssh into Volumio: How do I enable ssh connection?
SSH Connection | Volumio Developers Documentation

type:
cat /dev/input/event0
press buttons and see if there is a response, if yes note it down and continue to the next
cat /dev/input/event1
press buttons and see if there is a response, if yes note it down and continue to the next
cat /dev/input/eventx

so if in your case event1 and event2 triggers a response update the code:
mousea = InputDevice(‘/dev/input/event1’)
mouseb = InputDevice(‘/dev/input/event2’)

and change
for device in mousea, mouseb, mousec, moused:
to
for device in mousea, mouseb:

This is where my support stops.

Thanks for your patience. I had actually tried the cat commands before but figured I was doing something wrong as what I got back was this:
image

Surface Dial Button Push then Left Turn then Right Turn…

then your script would be something like this and save it as mouse.py in /home/volumio.

import asyncio
import struct
import time
import subprocess
import os

from evdev import InputDevice, categorize, ecodes

EV_VAL_PRESSED = 1
EV_VAL_RELEASED = 0
# mousea:
KEY_ENTER =      28
KEY_LEFT =       105
KEY_RIGHT =      106

mousea = InputDevice('/dev/input/event5')

print(mousea)

print('=== Start ===')

async def print_events(device):
    global  STATUS            
    async for event in device.async_read_loop():
        if event.type == ecodes.EV_KEY:
            if event.value == EV_VAL_PRESSED:
                if event.value == EV_VAL_PRESSED:
                    if event.code == KEY_ENTER:
                        print('<ENTER> Pressed')
                        command = 'volumio toggle'
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)                        

                    elif event.code == KEY_VOLUMEDOWN:
                        print('<VOLUMEDOWN> Pressed')
                        command = 'volumio volume minus'
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)      
                        
                    elif event.code == KEY_VOLUMEUP:
                        print('<VOLUMEUP> Pressed') 
                        command = 'volumio volume plus'
                        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE) 
           
                print(device.path, categorize(event), sep=': ')          
                print('---')

for device in mousea:
    asyncio.ensure_future(print_events(device))

loop = asyncio.get_event_loop()
loop.run_forever()
sudo apt-get update
sudo apt-get install -yf python3 python3-evdev

as service:
sudo nano /lib/systemd/system/btmouse.service

[Unit]
Description=BlueTooth Mouse Plugin
After=network.target
Wants=volumio.service
After=volumio.service
[Service]
ExecStart=/usr/bin/python3 /home/volumio/mouse.py

[Install]
WantedBy=multi-user.target

save with CTRL+o, CTRL+x

sudo chmod 644 /lib/systemd/system/btmouse.service && sudo systemctl daemon-reload && sudo systemctl enable btmouse.service && sudo systemctl start btmouse.service

Does this look like the issue?

http://logs.volumio.org/volumio/dzBfwnj.html

Aug 01 01:33:29 volumio systemd[1]: Started BlueTooth Mouse Plugin.
Aug 01 01:33:29 volumio sudo[3346]: pam_unix(sudo:session): session closed for user root
Aug 01 01:33:29 volumio python3[3349]: device /dev/input/event5, name “Surface Dial System Multi Axis”, phys “D8:3A:DD:AD:26:C5”
Aug 01 01:33:29 volumio python3[3349]: === Start ===
Aug 01 01:33:29 volumio python3[3349]: Traceback (most recent call last):
Aug 01 01:33:29 volumio python3[3349]: File “/home/volumio/mouse.py”, line 46, in
Aug 01 01:33:29 volumio python3[3349]: for device in mousea:
Aug 01 01:33:29 volumio python3[3349]: TypeError: ‘InputDevice’ object is not iterable
Aug 01 01:33:29 volumio systemd[1]: btmouse.service: Main process exited, code=exited, status=1/FAILURE
Aug 01 01:33:29 volumio systemd[1]: btmouse.service: Failed with result ‘exit-code’.

Also, are all the pcieport & nvme error messages normal?

This is because your device only has one event and this part doesn’t seems to accept only one event.

for device in mousea:
    asyncio.ensure_future(print_events(device))

or a very dirty fix:

for device in mousea, mousea:
    asyncio.ensure_future(print_events(device))

If the community wants to take over, please do, as I am not going to rewrite this. This is not related to Volumio support.

You can also take a look here, but involves a lot of adaption:

Last contribution on this topic:

cd ~
nano devices.py

copy/paste:

import evdev

for device in [evdev.InputDevice(fn) for fn in evdev.list_devices()]:
    print(device.fn, device.name, device.phys)

ctrl+x, enter ctrl+o

run and see which device name you dail has. In my example it’s “ZYSD.Ltd HCY RC”:
pyhton3 devices.py

volumio@motivo:~/scripts$ python3 devices.py
devices.py:4: DeprecationWarning: Please use InputDevice.path instead of InputDevice.fn
  print(device.fn, device.name, device.phys)
/dev/input/event0 ZYSD.Ltd HCY RC usb-fe9c0000.xhci-1.2/input0
/dev/input/event1 ZYSD.Ltd HCY RC Mouse usb-fe9c0000.xhci-1.2/input1
/dev/input/event2 ZYSD.Ltd HCY RC Consumer Control usb-fe9c0000.xhci-1.2/input1
/dev/input/event3 ZYSD.Ltd HCY RC System Control usb-fe9c0000.xhci-1.2/input1
/dev/input/event4 vc4-hdmi-0 vc4-hdmi-0/input0
/dev/input/event5 vc4-hdmi-1 vc4-hdmi-1/input0
/dev/input/event6 6-005d Goodix Capacitive TouchScreen input/ts

Replace mouse.py content with code below. Make sure you update DEVICENAME with yours.
It’s up to you to find the proper keys for your device. I helped you by printing the the key like:

You Pressed: KEY_UP
You Pressed: KEY_LEFT
You Pressed: KEY_RIGHT
You Pressed: KEY_DOWN
You Pressed: KEY_ENTER
You Pressed: KEY_HOMEPAGE
You Pressed: KEY_BACK
You Pressed: KEY_COMPOSE
You Pressed: KEY_VOLUMEDOWN
You Pressed: KEY_VOLUMEUP

also added all possible keys, so you only need to copy paste them in the handling of the button press.
This is now pretty universal.

sudo apt-get update
sudo apt-get install -yf python3 python3-evdev

as service:
sudo nano /lib/systemd/system/btmouse.service

[Unit]
Description=BlueTooth Mouse Plugin
After=network.target
Wants=volumio.service
After=volumio.service
[Service]
ExecStart=/usr/bin/python3 /home/volumio/mouse.py

[Install]
WantedBy=multi-user.target

save with CTRL+o, CTRL+x

sudo chmod 644 /lib/systemd/system/btmouse.service && sudo systemctl daemon-reload && sudo systemctl enable btmouse.service && sudo systemctl start btmouse.service

cd ~
nano mouse.py

Copy below code and save it: ctrl+0 => enter => ctrl+x

import evdev
import time
import subprocess
import asyncio
import struct
import os
from evdev import InputDevice, categorize, ecodes

DEVICENAME = "ZYSD.Ltd HCY"

EV_VAL_PRESSED = 1
EV_VAL_RELEASED = 0

KEY_RESERVED = 0
KEY_ESC = 1
KEY_1 = 2
KEY_2 = 3
KEY_3 = 4
KEY_4 = 5
KEY_5 = 6
KEY_6 = 7
KEY_7 = 8
KEY_8 = 9
KEY_9 = 10
KEY_0 = 11
KEY_MINUS = 12
KEY_EQUAL = 13
KEY_BACKSPACE = 14
KEY_TAB = 15
KEY_Q = 16
KEY_W = 17
KEY_E = 18
KEY_R = 19
KEY_T = 20
KEY_Y = 21
KEY_U = 22
KEY_I = 23
KEY_O = 24
KEY_P = 25
KEY_LEFTBRACE = 26
KEY_RIGHTBRACE = 27
KEY_ENTER = 28
KEY_LEFTCTRL = 29
KEY_A = 30
KEY_S = 31
KEY_D = 32
KEY_F = 33
KEY_G = 34
KEY_H = 35
KEY_J = 36
KEY_K = 37
KEY_L = 38
KEY_SEMICOLON = 39
KEY_APOSTROPHE = 40
KEY_GRAVE = 41
KEY_LEFTSHIFT = 42
KEY_BACKSLASH = 43
KEY_Z = 44
KEY_X = 45
KEY_C = 46
KEY_V = 47
KEY_B = 48
KEY_N = 49
KEY_M = 50
KEY_COMMA = 51
KEY_DOT = 52
KEY_SLASH = 53
KEY_RIGHTSHIFT = 54
KEY_KPASTERISK = 55
KEY_LEFTALT = 56
KEY_SPACE = 57
KEY_CAPSLOCK = 58
KEY_F1 = 59
KEY_F2 = 60
KEY_F3 = 61
KEY_F4 = 62
KEY_F5 = 63
KEY_F6 = 64
KEY_F7 = 65
KEY_F8 = 66
KEY_F9 = 67
KEY_F10 = 68
KEY_NUMLOCK = 69
KEY_SCROLLLOCK = 70
KEY_KP7 = 71
KEY_KP8 = 72
KEY_KP9 = 73
KEY_KPMINUS = 74
KEY_KP4 = 75
KEY_KP5 = 76
KEY_KP6 = 77
KEY_KPPLUS = 78
KEY_KP1 = 79
KEY_KP2 = 80
KEY_KP3 = 81
KEY_KP0 = 82
KEY_KPDOT = 83
KEY_ZENKAKUHANKAKU = 85
KEY_102ND = 86
KEY_F11 = 87
KEY_F12 = 88
KEY_RO = 89
KEY_KATAKANA = 90
KEY_HIRAGANA = 91
KEY_HENKAN = 92
KEY_KATAKANAHIRAGANA = 93
KEY_MUHENKAN = 94
KEY_KPJPCOMMA = 95
KEY_KPENTER = 96
KEY_RIGHTCTRL = 97
KEY_KPSLASH = 98
KEY_SYSRQ = 99
KEY_RIGHTALT = 100
KEY_LINEFEED = 101
KEY_HOME = 102
KEY_UP = 103
KEY_PAGEUP = 104
KEY_LEFT = 105
KEY_RIGHT = 106
KEY_END = 107
KEY_DOWN = 108
KEY_PAGEDOWN = 109
KEY_INSERT = 110
KEY_DELETE = 111
KEY_MACRO = 112
KEY_MUTE = 113
KEY_VOLUMEDOWN = 114
KEY_VOLUMEUP = 115
KEY_POWER = 116  # SC System Power Down
KEY_KPEQUAL = 117
KEY_KPPLUSMINUS = 118
KEY_PAUSE = 119
KEY_SCALE = 120
KEY_KPCOMMA = 121
KEY_HANGEUL = 122
KEY_HANJA = 123
KEY_YEN = 124
KEY_LEFTMETA = 125
KEY_RIGHTMETA = 126
KEY_COMPOSE = 127
KEY_STOP = 128  # AC Stop
KEY_AGAIN = 129
KEY_PROPS = 130  # AC Properties
KEY_UNDO = 131  # AC Undo
KEY_FRONT = 132
KEY_COPY = 133  # AC Copy
KEY_OPEN = 134  # AC Open
KEY_PASTE = 135  # AC Paste
KEY_FIND = 136  # AC Search
KEY_CUT = 137  # AC Cut
KEY_HELP = 138  # AL Integrated Help Center
KEY_MENU = 139  # Menu (show menu)
KEY_CALC = 140  # AL Calculator
KEY_SETUP = 141
KEY_SLEEP = 142  # SC System Sleep
KEY_WAKEUP = 143  # System Wake Up
KEY_FILE = 144  # AL Local Machine Browser
KEY_SENDFILE = 145
KEY_DELETEFILE = 146
KEY_XFER = 147
KEY_PROG1 = 148
KEY_PROG2 = 149
KEY_WWW = 150  # AL Internet Browser
KEY_MSDOS = 151
KEY_COFFEE = 152  # AL Terminal Lock/Screensaver
KEY_ROTATE_DISPLAY = 153  # Display orientation for e.g. tablets
KEY_DIRECTION = 153
KEY_CYCLEWINDOWS = 154
KEY_MAIL = 155
KEY_BOOKMARKS = 156  # AC Bookmarks
KEY_COMPUTER = 157
KEY_BACK = 158  # AC Back
KEY_FORWARD = 159
KEY_CLOSECD = 160
KEY_EJECTCD = 161
KEY_EJECTCLOSECD = 162
KEY_NEXTSONG = 163
KEY_PLAYPAUSE = 164
KEY_PREVIOUSSONG = 165
KEY_STOPCD = 166
KEY_RECORD = 167
KEY_REWIND = 168
KEY_PHONE = 169  # Media Select Telephone
KEY_ISO = 170
KEY_CONFIG = 171  # AL Consumer Control Configuration
KEY_HOMEPAGE = 172  # AC Home
KEY_REFRESH = 173  # AC Refresh
KEY_EXIT = 174  # AC Exit
KEY_MOVE = 175
KEY_EDIT = 176
KEY_SCROLLUP = 177
KEY_SCROLLDOWN = 178
KEY_KPLEFTPAREN = 179
KEY_KPRIGHTPAREN = 180
KEY_NEW = 181  # AC New
KEY_REDO = 182  # AC Redo/Repeat
KEY_F13 = 183
KEY_F14 = 184
KEY_F15 = 185
KEY_F16 = 186
KEY_F17 = 187
KEY_F18 = 188
KEY_F19 = 189
KEY_F20 = 190
KEY_F21 = 191
KEY_F22 = 192
KEY_F23 = 193
KEY_F24 = 194
KEY_PLAYCD = 200
KEY_PAUSECD = 201
KEY_PROG3 = 202
KEY_PROG4 = 203
KEY_ALL_APPLICATIONS = 204  # AC Desktop Show All Applications
KEY_DASHBOARD = 204
KEY_SUSPEND = 205
KEY_CLOSE = 206  # AC Close
KEY_PLAY = 207
KEY_FASTFORWARD = 208
KEY_BASSBOOST = 209
KEY_PRINT = 210  # AC Print
KEY_HP = 211
KEY_CAMERA = 212
KEY_SOUND = 213
KEY_QUESTION = 214
KEY_EMAIL = 215
KEY_CHAT = 216
KEY_SEARCH = 217
KEY_CONNECT = 218
KEY_FINANCE = 219  # AL Checkbook/Finance
KEY_SPORT = 220
KEY_SHOP = 221
KEY_ALTERASE = 222
KEY_CANCEL = 223  # AC Cancel
KEY_BRIGHTNESSDOWN = 224
KEY_BRIGHTNESSUP = 225
KEY_MEDIA = 226
KEY_SWITCHVIDEOMODE = 227  # Cycle between available video outputs (Monitor/LCD/TV-out/etc)
KEY_KBDILLUMTOGGLE = 228
KEY_KBDILLUMDOWN = 229
KEY_KBDILLUMUP = 230
KEY_SEND = 231  # AC Send
KEY_REPLY = 232  # AC Reply
KEY_FORWARDMAIL = 233  # AC Forward Msg
KEY_SAVE = 234  # AC Save
KEY_DOCUMENTS = 235
KEY_BATTERY = 236
KEY_BLUETOOTH = 237
KEY_WLAN = 238
KEY_UWB = 239
KEY_UNKNOWN = 240
KEY_VIDEO_NEXT = 241  # drive next video source
KEY_VIDEO_PREV = 242  # drive previous video source
KEY_BRIGHTNESS_CYCLE = 243  # brightness up, after max is min
KEY_BRIGHTNESS_AUTO = 244  # Set Auto Brightness: manual brightness control is off, rely on ambient
KEY_DISPLAY_OFF = 245  # display device to off state
KEY_WWAN = 246  # Wireless WAN (LTE, UMTS, GSM, etc.)
KEY_RFKILL = 247  # Key that controls all radios
KEY_MICMUTE = 248  # Mute / unmute the microphone
BTN_MOUSE = 272
BTN_LEFT = 272
BTN_RIGHT = 273
BTN_MIDDLE = 274

devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
        
async def print_events(device):
    global  STATUS            
    async for event in device.async_read_loop():
        if event.type == ecodes.EV_KEY:
            if event.value == EV_VAL_PRESSED:
                try:
                    print("You Pressed: " + ecodes.KEY[event.code])
                except:
                    print("You Pressed: " + ecodes.KEY[event.code][1])

                if event.code == KEY_ENTER:
                    command = 'volumio toggle'

                elif event.code == KEY_VOLUMEDOWN:
                    command = 'volumio volume minus'

                elif event.code == KEY_VOLUMEUP:
                    command = 'volumio volume plus'

                process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
for device in devices:
    dev = InputDevice(device.path)
    if dev.name.find(DEVICENAME) >= 0:
        print(device.path, dev.name)
        asyncio.ensure_future(print_events(device))

loop = asyncio.get_event_loop()
loop.run_forever()