Reverse Engineering a LumeCube

One of our upcoming hardware projects (more on this later) requires a very bright, controllable light source. In the past, we’ve just used bare LEDs with our own cooling, power, control, etc. Not being someone who really understands how electricity works though, it always feels like a hassle to get a reliable setup. For this project, we instead decided to just give a LumeCube a try. They’re far from cheap, but they’re very bright, and hopefully have rock solid reliability.

LumeCube has an iOS app which allows for control over bluetooth. Although it also has a USB port, that’s only used for charging. We wanted to be able to do basic brightness control from within the Python application that will run the rest of the hardware. It doesn’t appear anyone has gone to the trouble of reverse engineering the LumeCube before, so we figured we’d give it a go. There were just two challenges:

  1. We’ve never reverse engineered a Bluetooth communications protocol.
  2. We don’t really understand how Bluetooth works.

Sticking with our philosophy of obtaining minimum-viable-knowledge, we started Googling “how to reverse engineer a bluetooth device” and ended up on this Medium post by Uri Shaked. Uri pointed us towards the “nRF Connect” app, which allows you to scan for Bluetooth devices and enumerate all of their characteristics (look at me using the lingo).

Acting on the assumption that LumeCube wasn’t going to go out of their way to secure their Bluetooth connection, we assumed brightness control would be pretty straightforward. It was just a matter of tracking down the characteristic ID and the structure of the data. To do that, we began by listing all of the characteristics in nRF Connect. Then we would disconnect and launch the official app. From there, we could adjust the brightness, then flip back to nRF Connect, rescan the characteristics, and see what had changed.

The relevant characteristic popped out very quickly. The third byte of 33826a4d-486a-11e4-a545-022807469bf0 varied from 0x00 to 0x64, or 0-100. Writing new values back to that characteristic confirmed that hunch – huzzah!

Once we’d identified the characteristic, it was just a matter of implementing some controls in Python. For that we used Bleak, which offers good documentation and cross-platform support. Below is a quick example that turns brightness to 100 (0x64).

import asyncio
import logging
from bleak import BleakClient

address = "819083F8-A230-4F61-8F94-CB69FF63D340"
LIGHT_UUID = "33826a4d-486a-11e4-a545-022807469bf0"
#logging.basicConfig(level=logging.DEBUG)

async def run(address):
    async with BleakClient(address) as client:
        await client.write_gatt_char(LIGHT_UUID, bytearray(b"\xfc\xa1\x64\x00"), True)
loop = asyncio.get_event_loop()
loop.run_until_complete(run(address))

Leave a Reply