Hi Guys, just curious if anyone has done any work on the Microsoft Surface Dial This one?
I’m thinking about it as a way to physically control Goole Home Audio.
Hi Guys, just curious if anyone has done any work on the Microsoft Surface Dial This one?
I’m thinking about it as a way to physically control Goole Home Audio.
Any updates on this? I’d also be curious.
Necroing this thread, as it appears to contain the most up to date status information on Surface Dial integration.
I just ordered three of them because they’re getting discontinued. Note: I have not received them, and have no hands on experience. But the hardware looks solid, it uses standard batteries, and, importantly, they have driver support in linux: shortcut keys - Microsoft Surface Dial on Linux - Ask Ubuntu. Once there are open source drivers for something, it tends not to suddenly stop working.
So you can set it up as a keyboard on linux, then use Keyboard Remote - Home Assistant (home-assistant.io) to integrate the keypresses into home assistant.
This is all theoretical at this stage: I don’t have the things. But thought it might help someone at least get started in hopefully the right direction.
I also got the idea to use the Surface Dial I have hanging around. My original idea was to use an application called Positron to send keystrokes to Home Assistant: GitHub - agustinmista/positron: Effortlessly control your Home Assistant devices using keyboard shortcuts.
From there, I can make a custom tool from the Wheel page in the Windows Setting app, which lets me set custom hotkeys for clockwise rotation, counterclockwise rotation, and press. The glaring issue with this setup is that Windows only lets you set 1 global “Custom Tool”. I wanted to have one tool control brightness and another control color temperature, but that’s not possible with this method.
Alternatively, you can set a seemingly endless amount of Application Specific custom tools, but you have to have that application in the foreground. I tried making custom tools for Windows Explorer, hoping that it would be global since, you know, Windows Explorer is always running, but no dice. Those tools don’t show in the radial menu unless Windows Explorer is actually the focused application.
If you come up with a better solution, please let me know!
Edit: In case anyone wanted to see how this is set up, check these out:
Sorry about the imgur link not being a link, apparently I can only include two links since I just created my user here.
Mods, sorry for sneaking around this rule, but hopefully it’s okay
Well one of the three arrived and I’ve played with it this morning. The batteries had leaked so badly I’m surprised it worked once I cleaned it up. They are new devices, so could have returned, but I’m not too fussed about a bit of mess.
I should have come back to the thread to see your message before barreling ahead! As it turns out, the keyboard plugin is not particularly useful, since it runs on the home assistant server, which is fine if you’re within bluetooth range, but not so if you’re wanting to use it across your home. Had I seen your thread I would have used positron (assuming it’s available on linux) rather than rolling my own…
NO WARRANTIES OR IMPLIED ASSISTANCE ETC…
The solution I’ve come up with is to fire off homeassistant events from a linux machine I’ve connected it to.
You connect it to the linux machine using bluetoothctl, in the usual way.
To have it send messages, you then need to identify the device number (I could have done this in the python script, but it’s more a hack than a release). You want to find the "Surface Dial System Multi Axis):
$ evtest
No device specified, trying to scan all of /dev/input/event*
Not running as root, no devices may be available.
Available devices:
/dev/input/event0: axp20x-pek
/dev/input/event1: sunxi-ir
/dev/input/event2: Surface Dial System Multi Axis
/dev/input/event3: Surface Dial System Control
Here it’s /dev/input/event2. Note that you may have to wake it up if you’ve connected it and it’s not showing up. These things seem to like sleeping.
Then create what home assistant calls a ‘long-lived access token’. (Hint, it’s not anywhere you would expect it to be. Hint 2: Go down to your profile in the lower left hand corner!) Copy it to a text file somewhere because I don’t think you can access it again.
Then configure the following python script on the linux box you’ve connected the dial to. I’ve put the bits you need to configure in between “START CONFIG” and “END CONFIG”. Note that “True” and “False” are upper-case in python.
from evdev import InputDevice, categorize, ecodes, KeyEvent
import time
import requests
import urllib3
# avoid getting flooded by warnings if self-signed ssl cert
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# START CONFIG
# see what it is with ./evtest
device_path = '/dev/input/event2'
# set to home assistant url -- if self-signed, will need to NOT verify SSL
home_assistant_url = 'https://homeassistant.local:8123'
verify_ssl=False
# event names - what event name to fire
button_push = 'play-pause-beddial'
rotate_dial = 'volume-change-beddial'
# END CONFIG
home_assistant_api = home_assistant_url + '/api/events/'
home_assistant_playpause = home_assistant_api + button_push
home_assistant_volume_change = home_assistant_api + rotate_dial
home_assistant_token = '[your actual long-lived token]'
headers = {
"Authorization": f"Bearer {home_assistant_token}",
"content-type": "application/json",
}
while True:
try:
device = InputDevice(device_path)
for event in device.read_loop():
if event. Type == ecodes.EV_KEY:
keyevent = categorize(event)
if ((keyevent.keycode[0] == 'BTN_0') or (keyevent.keycode[0] == 'BTN_MISC')):
if keyevent.keystate == KeyEvent.key_down:
response = requests.post(home_assistant_playpause, headers=headers, verify=verify_ssl)
elif event.type == ecodes.EV_REL:
data = {"value": event. Value}
response = requests.post(home_assistant_volume_change, headers=headers, json=data, verify=verify_ssl)
except (FileNotFoundError, OSError):
print(f"Device at {device_path} disconnected. Waiting for device to reconnect...")
time. Sleep(1)
You’ll need to install python3, python3-evdev, python3-requests, and python3-urllib3. At least I think that’s everything I needed to install, on an armbian orangepi3LTS.
Pushing the button should send the event you’ve configured it to, and same with rotation. The rotation also includes a bit of data showing the value: positive for clockwise; negative for counter-clockwise. E.g. from my homeassistant’s Developer Tools → Events → Listen to events, subscribing to “play-pause-beddial” gives me the following events:
event_type: volume-change-beddial
data:
value: -3
origin: REMOTE
time_fired: "2023-05-27T01:43:34.936135+00:00"
context:
id: 01H1DE058RB7DEZME389BVP1NN
parent_id: null
user_id: f1b21ed23966482f9618f9acb113a960
And for hitting the button, I get (after subscribing to play-pause-beddial) these things:
event_type: play-pause-beddial
data: {}
origin: REMOTE
time_fired: "2023-05-27T01:50:09.970372+00:00"
context:
id: 01H1DEC71J6MB426EBA0YW06Y2
parent_id: null
user_id: f1b21ed23966482f9618f9acb113a960
The above is the extent of the testing I’ve done with it. It would need to be setup I think as a daemon under systemd. It may crash randomly. I don’t know.
I have to say, the physical build quality is extremely nice (apart from where it’s been burnt to bits by leaking batteries). And it’s nice it uses AAA batteries instead of the usual baby-killers in some weird form factor that you can only find on Aliexpress from DeadWithinAWeekFire. However, the fact that it goes to sleep all the time means I would have to recommend against these in favour of the hue tap dial thing. I got one of those too, and although it was also a bit of nuisance to setup, it did not need a linux client, and does not go to sleep.
After integrating into homeassistant “automation”, it’s pretty clear that for what I wanted to do with it – volume control – everything’s too slow for it to work as is. The volume changes occur over maybe 5 seconds+, bit by bit. The natural reaction is to keep adjusting the volume until it’s full or zero once it finally finished adjusting.
So I incorporated a client-side buffer, only sending the volume change event to homeassistant every 300ms (if there’s any event to send). A bit like debouncing. 300ms seems to achieve the most snappiness, and is good enough that you don’t reach to try again. Note that in the home assistant automation, you need to set it to ‘queue’ as the mode–it’s the three dots in the top right hand corner → “Change mode”.
from evdev import InputDevice, categorize, ecodes, KeyEvent
import time
import requests
import urllib3
from threading import Thread
from queue import Queue
# avoid getting flooded by warnings if self-signed ssl cert
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# START CONFIG
# see what it is with ./evtest
device_path = '/dev/input/event2'
# set to home assistant url -- if self-signed, will need to NOT verify SSL
home_assistant_url = 'https://homeassistant.local:8123'
verify_ssl=False
# event names - what event name to fire
button_push = 'play-pause-beddial'
rotate_dial = 'volume-change-beddial'
# Long lived token
home_assistant_token = '[your long lived token]'
# END CONFIG
home_assistant_api = home_assistant_url + '/api/events/'
home_assistant_playpause = home_assistant_api + button_push
home_assistant_volume_change = home_assistant_api + rotate_dial
headers = {
"Authorization": f"Bearer {home_assistant_token}",
"content-type": "application/json",
}
# Queue for storing volume changes
volume_changes = Queue()
def send_volume_changes():
while True:
if not volume_changes.empty():
total_change = 0
while not volume_changes.empty():
total_change += volume_changes.get()
print(total_change)
data = {"value": total_change}
response = requests.post(home_assistant_volume_change, headers=headers, json=data, verify=verify_ssl)
time.sleep(0.3) # 300ms delay
# Start the worker thread
volume_thread = Thread(target=send_volume_changes)
volume_thread.start()
while True:
try:
device = InputDevice(device_path)
for event in device.read_loop():
if event.type == ecodes.EV_KEY:
keyevent = categorize(event)
if ((keyevent.keycode[0] == 'BTN_0') or (keyevent.keycode[0] == 'BTN_MISC')):
if keyevent.keystate == KeyEvent.key_down:
response = requests.post(home_assistant_playpause, headers=headers, verify=verify_ssl)
elif event.type == ecodes.EV_REL:
volume_changes.put(event.value)
except (FileNotFoundError, OSError):
print(f"Device at {device_path} disconnected. Waiting for device to reconnect...")
time. Sleep(1)
Are you planning to only use it for volume? I keep trying to think of some way to have some sort of a mode changing ability, but I can’t find a good way that it could be done
(I just got the additional two dials I ordered. Both of them are in perfect condition with no battery leakage.)
I am using it just for volume and pause. You could do a long-press to indicate a mode change, but won’t have visual feedback. I have set up other similar buttons to switch between whatever speakers I have in the room and bluetooth headphones, which is pretty handy. I’ll probably do the same with this.
(Edit because I hit the wrong button and it posted half a post…)