Canon DSLR Bluetooth Remote Protocol

This is a continuation of my earlier reverse engineering work.

This time, I’ve succeeded in duplicated all of the remote’s functionality, other than things like firmware updating.

Determining the Protcol

It turns out my previous testing failed for a simple reason: I didn’t know the camera has to be set to a particular mode to use a remote. Specifically, self-timer/remote mode. After that, my script to try writting all possible values to a GATT descriptor ended up triggering the shutter.

from subprocess import Popen, PIPE
import sys

device = sys.argv[1]
port = sys.argv[2] 
n = int(sys.argv[3])

p = Popen(['btgatt-client', '-d', device], stdout=PIPE, stdin=PIPE, universal_newlines=True)

def wait_contain(s):
    while True:
        line = p.stdout.readline()
        line = line.replace("\033[0;94m[GATT client]\033[0m#", '')
        line = line.strip()
        if line:
            print(line)
        if s in line:
            break

wait_contain("GATT discovery procedures complete")

print("write-value 0xf504 0x03")
p.stdin.write("write-value 0xf504 0x03\n")
p.stdin.flush()

wait_contain("Write successful")

for i in range(0, 256 ** n - 1):
    text = f"write-value {port} " + ' '.join(reversed([str((i // (256 ** j)) % 256) for j in range(n)]))
    print(text)
    p.stdin.write(text + '\n')
    p.stdin.flush()
    wait_contain("Write successful")

for line in p.stdout.readlines():
    print(line)

Of the writable descriptors, I chose 0xf506 because writting to it resulted in the activity indicator on the camera flashing, which seemed promising. Pass the script three arguments: the Bluetooth MAC address of the camera, 0xf506, and 1. The last value is the number of bytes to try writing.

In addition to testing using btgatt-client, I looked at the ROM’s disassembly. If you want to take a look at that, use the ROM dumper from Magic Lantern, and look search the disassembly for RecvAccrcCamCom. That function tests various bits of the number used to represent commands from the BR-E1 remote.

The Protocol

As I said above, commands are one byte, written to GATT descriptor 0xf506. The command is a bitflag, of sorts:

  • Bits 0-1 are unused and ignored, as far as I can tell.
  • Bit 2-3 indicates the mode the remote is in.
    • 00 is invalid
    • 11 is “immediate release”, without a delay
    • 01 enables a two second timer
    • 10 is movie mode
  • Bits 4-7 indicate buttons.
    • Bit 4 is “wide” and 5 is “tele”, based on disassembly; zooming requires a “Power Zoom Adapter” so I haven’t tested it.
    • Bit 6 is autofocus
    • Bit 7 is shutter release

Using this information, I’ve written a script providing a very basic implementation of the protocol.