212 lines
9.7 KiB
Markdown
212 lines
9.7 KiB
Markdown
# SiriRemote-Linux
|
|
|
|
This project allows the usage of an Apple TV Siri Remote with Linux.
|
|
|
|
Do you have an old remote lying around and would like to control your linux machine with it? Then this is for you. This
|
|
python program connects to and intercepts the data from a SiriRemote over bluetooth and does something useful with it,
|
|
like change the volume or control media. Even the touchpad is working and if you know a bit of python, you can basically
|
|
do anything with it.
|
|
|
|
Suppoerted models ([check which one you have](https://support.apple.com/en-us/HT205329)):
|
|
- 1st gen
|
|
- 2nd gen ([gen-3]((../gen-3)) branch)
|
|
- 3rd gen ([gen-3]((../gen-3)) branch)
|
|
- yours might also work, try it out!
|
|
|
|
* [SiriRemote-Linux](#siriremote-linux)
|
|
* [Usage](#usage)
|
|
* [Preparations (only once)](#preparations-only-once)
|
|
* [Media control](#media-control)
|
|
* [Custom](#custom)
|
|
* [Internal working of the SiriRemote](#internal-working-of-the-siriremote)
|
|
* [GATT Layout](#gatt-layout)
|
|
* [Battery information](#battery-information)
|
|
* [Battery level](#battery-level)
|
|
* [Charging state](#charging-state)
|
|
* [Enable input](#enable-input)
|
|
* [Buttons](#buttons)
|
|
* [Touch](#touch)
|
|
* [X coordinate](#x-coordinate)
|
|
* [Y coordinate](#y-coordinate)
|
|
* [Audio/Siri](#audiosiri)
|
|
|
|
## Usage
|
|
|
|
### Preparations (only once)
|
|
|
|
Pair the remote with you machine:
|
|
|
|
```commandline
|
|
bluetoothctl
|
|
power on
|
|
scan on
|
|
```
|
|
|
|
Press `MENU` and `+` for few seconds and the remote will show up in bluetoothctl. The mac address might start
|
|
with `48:A9:1C:` or `60:BE:C4:` (yours may vary, [see here](https://maclookup.app/vendors/apple-inc)).
|
|
|
|
```commandline
|
|
pair <mac-address>
|
|
disconnect <mac-address>
|
|
exit
|
|
```
|
|
|
|
Install the python dependencies:
|
|
|
|
```commandline
|
|
pip install bluepy evdev
|
|
```
|
|
|
|
### Media control
|
|
|
|
This will connect to the remote and simulate an input device for your machine.
|
|
|
|
Run the main program (`<mac-address>` being the remote mac address)
|
|
|
|
```commandline
|
|
sudo python ./main.py <mac-address>
|
|
```
|
|
|
|
Press any button on the remote, and you should now be able to control the volume, media and the mouse cursor (menu /
|
|
airplay = prev. / next song). The remote will disconnect after a while of inactivity but as soon as you press any button
|
|
it will reconnect.
|
|
|
|
### Custom
|
|
|
|
This repo provides a [SiriRemote](remote/remote.py) class which you can use to easily interface with the remote and
|
|
receive button and touchpad events. You can see a simple example for it in [echo_test.py](echo_test.py).
|
|
|
|
# Internal working of the SiriRemote
|
|
|
|
Huge thanks to [Jack-R1](https://github.com/Jack-R1). Without his previous work, I wouldn't have made it this far.
|
|
|
|
The remote communicates with HID over GATT, but since it's an Apple product you need to do some additional stuff, just
|
|
to
|
|
receive data. And even then it doesn't work on its own.
|
|
|
|
## GATT Layout
|
|
|
|
```
|
|
Service: "Generic Access" | UUID: 00001800-0000-1000-8000-00805f9b34fb | Handle: 0x1
|
|
Char: "Device Name" | UUID: 00002a00-0000-1000-8000-00805f9b34fb | Handle: 0x2 | Value Handle: 0x3
|
|
Char: "Appearance" | UUID: 00002a01-0000-1000-8000-00805f9b34fb | Handle: 0x4 | Value Handle: 0x5
|
|
Service: "Generic Attribute" | UUID: 00001801-0000-1000-8000-00805f9b34fb | Handle: 0x6
|
|
Char: "Service Changed" | UUID: 00002a05-0000-1000-8000-00805f9b34fb | Handle: 0x7 | Value Handle: 0x8
|
|
Desc: "Client Characteristic Configuration" | UUID: 00002902-0000-1000-8000-00805f9b34fb | Handle: 0x9
|
|
Service: "Device Information" | UUID: 0000180a-0000-1000-8000-00805f9b34fb | Handle: 0xa
|
|
Char: "Serial Number String" | UUID: 00002a25-0000-1000-8000-00805f9b34fb | Handle: 0xb | Value Handle: 0xc
|
|
Char: "Hardware Revision String" | UUID: 00002a27-0000-1000-8000-00805f9b34fb | Handle: 0xd | Value Handle: 0xe
|
|
Char: "Firmware Revision String" | UUID: 00002a26-0000-1000-8000-00805f9b34fb | Handle: 0xf | Value Handle: 0x10
|
|
Char: "Manufacturer Name String" | UUID: 00002a29-0000-1000-8000-00805f9b34fb | Handle: 0x11 | Value Handle: 0x12
|
|
Char: "PnP ID" | UUID: 00002a50-0000-1000-8000-00805f9b34fb | Handle: 0x13 | Value Handle: 0x14
|
|
Service: "Human Interface Device" | UUID: 00001812-0000-1000-8000-00805f9b34fb | Handle: 0x15
|
|
Char: "HID Information" | UUID: 00002a4a-0000-1000-8000-00805f9b34fb | Handle: 0x16 | Value Handle: 0x17
|
|
Char: "Report Map" | UUID: 00002a4b-0000-1000-8000-00805f9b34fb | Handle: 0x18 | Value Handle: 0x19
|
|
Char: "HID Control Point" | UUID: 00002a4c-0000-1000-8000-00805f9b34fb | Handle: 0x1a | Value Handle: 0x1b
|
|
Char: "Report" | UUID: 00002a4d-0000-1000-8000-00805f9b34fb | Handle: 0x1c | Value Handle: 0x1d
|
|
Desc: "Report Reference" | UUID: 00002908-0000-1000-8000-00805f9b34fb | Handle: 0x1e
|
|
Char: "Report" | UUID: 00002a4d-0000-1000-8000-00805f9b34fb | Handle: 0x1f | Value Handle: 0x20
|
|
Desc: "Report Reference" | UUID: 00002908-0000-1000-8000-00805f9b34fb | Handle: 0x21
|
|
Char: "Report" | UUID: 00002a4d-0000-1000-8000-00805f9b34fb | Handle: 0x22 | Value Handle: 0x23
|
|
Desc: "Client Characteristic Configuration" | UUID: 00002902-0000-1000-8000-00805f9b34fb | Handle: 0x24
|
|
Desc: "Report Reference" | UUID: 00002908-0000-1000-8000-00805f9b34fb | Handle: 0x25
|
|
Service: "Battery Service" | UUID: 0000180f-0000-1000-8000-00805f9b34fb | Handle: 0x26
|
|
Char: "Battery Level" | UUID: 00002a19-0000-1000-8000-00805f9b34fb | Handle: 0x27 | Value Handle: 0x28
|
|
Desc: "Client Characteristic Configuration" | UUID: 00002902-0000-1000-8000-00805f9b34fb | Handle: 0x29
|
|
Char: "2a1a" | UUID: 00002a1a-0000-1000-8000-00805f9b34fb | Handle: 0x2a | Value Handle: 0x2b
|
|
Desc: "Client Characteristic Configuration" | UUID: 00002902-0000-1000-8000-00805f9b34fb | Handle: 0x2c
|
|
Service: "Bond Management" | UUID: 0000181e-0000-1000-8000-00805f9b34fb | Handle: 0x2d
|
|
Char: "Bond Management Control Point" | UUID: 00002aa4-0000-1000-8000-00805f9b34fb | Handle: 0x2e | Value Handle: 0x2f
|
|
Char: "Bond Management Feature" | UUID: 00002aa5-0000-1000-8000-00805f9b34fb | Handle: 0x30 | Value Handle: 0x31
|
|
Service: "8341f2b4-c013-4f04-8197-c4cdb42e26dc" | UUID: 8341f2b4-c013-4f04-8197-c4cdb42e26dc | Handle: 0x32
|
|
Char: "9fbf120d-6301-42d9-8c58-25e699a21dbd" | UUID: 9fbf120d-6301-42d9-8c58-25e699a21dbd | Handle: 0x33 | Value Handle: 0x34
|
|
Char: "2bdcaebe-8746-45df-a841-96b840980fb7" | UUID: 2bdcaebe-8746-45df-a841-96b840980fb7 | Handle: 0x35 | Value Handle: 0x36
|
|
Char: "2bdcaebe-8746-45df-a841-96b840980fb8" | UUID: 2bdcaebe-8746-45df-a841-96b840980fb8 | Handle: 0x37 | Value Handle: 0x38
|
|
Char: "30e69638-3752-4feb-a3aa-3226bcd05ace" | UUID: 30e69638-3752-4feb-a3aa-3226bcd05ace | Handle: 0x39 | Value Handle: 0x3a
|
|
Desc: "Client Characteristic Configuration" | UUID: 00002902-0000-1000-8000-00805f9b34fb | Handle: 0x3b
|
|
```
|
|
|
|
## Battery information
|
|
|
|
### Battery level
|
|
|
|
Enable notifications on handle `0x0027`, by writing `0x01 0x00` to `0x0029`. You'll then receive values from `0x0028`
|
|
ranging from `0x00` to `0x64`, which are 0 to 100 as integer and represent the battery percentage.
|
|
|
|
### Charging state
|
|
|
|
Enable notifications on handle `0x002a`, by writing `0x01 0x00` to `0x002c`. Possible values you'll receive
|
|
from `0x002b` are:
|
|
|
|
- `0xAB` charging
|
|
- `0xAF` discharging
|
|
- `0xBB` plugged in
|
|
|
|
## Enable input
|
|
|
|
To receive input data from the remote we need to send `0xAF` to the handle `0x001d` and also enable notifications
|
|
on handle `0x0022`, by writing `0x01 0x00` to `0x0024`. You'll then receive byte arrays from `0x0023` with a length of
|
|
either 2, 13, 20 or 101[^audio].
|
|
|
|
### Buttons
|
|
|
|
Button presses are sent with the second byte and are encoded bitwise, so it also supports multiple presses:
|
|
|
|
- `0x00` All released
|
|
- `0x01` AirPlay
|
|
- `0x02` Volume up
|
|
- `0x04` Volume down
|
|
- `0x08` Play/Pause
|
|
- `0x10` Siri
|
|
- `0x20` Menu
|
|
- `0x80` Touchpad
|
|
|
|
### Touch
|
|
|
|
Touch events are sent with a 13 byte array (or 20 with two fingers). The first 6 are general information and the
|
|
following 7 are the data of the touch position.
|
|
|
|
| Index | Content |
|
|
|-------|-------------------------------|
|
|
| 0 | Finger count |
|
|
| 1 | [Pressed buttons](#Buttons) |
|
|
| 2 | always 50 |
|
|
| 3 | ? |
|
|
| 4 | ? |
|
|
| 5 | ? (increases over time) |
|
|
| | |
|
|
| 6 | [X coordinate](#X-coordinate) |
|
|
| 7 | [X coordinate](#X-coordinate) |
|
|
| 8 | [Y coordinate](#Y-coordinate) |
|
|
| 9 | ? (0 when released) |
|
|
| 10 | ? (0 when released) |
|
|
| 11 | pressure |
|
|
| 12 | ? |
|
|
| | |
|
|
| 13-19 | see 6-12 (second finger) |
|
|
|
|
#### X coordinate
|
|
|
|
The X coordinate consists of two bytes at the indices 6 and 7. However only the first 3 bits of the byte at index 7 are
|
|
required. The SiriRemote's touchpad has the weird property of having 8 vertical "zones" and the byte at index 6 only
|
|
gives us the location in such a "zone". The byte at index 7 gives us the remaining information for the "zone" index.
|
|
|
|
`x = data[6] + 255 * (data[7] & 7)`
|
|
|
|
#### Y coordinate
|
|
|
|
The Y coordinate is pretty straight forward. The value (bottom to top) goes from 188 to 255 and then from 0 to 38. I
|
|
assume it's a signed bytes which would explain this behaviour.
|
|
|
|
The resolution is pretty low compared to the X coordinate, so there might be something else that contributes to the Y
|
|
coordinate, that I haven't found yet.
|
|
|
|
### Audio/Siri
|
|
|
|
Unfortunately I wasn't able to test the data we receive from the microphone, because of a linux limitation[^audio].
|
|
|
|
All I know for now is that you're supposed to get 101 bytes of which the majority is opus encoded audio data. You can
|
|
check out [Jack-R1](https://github.com/Jack-R1)'s repos for more information about this.
|
|
|
|
[^audio]: 101 bytes are received when Siri/Audio is used, but bluez somehow can't handle more than 20 bytes. The bytes
|
|
do however appear in wireshark. |