Reverse Engineering the Ubiquiti Cloud Key Gen2 Display

July 22, 2019

Demo of Cloud Key screen control

I recently picked up Ubiquiti’s latest UniFi Cloud Key that includes a nice LCD stats display.

While playing with it, I remembered that it used to be possible to control UniFi device LEDs via an SSH session:

echo 255 > /sys/class/leds/blue/brightness # LED on
echo 0 > /sys/class/leds/blue/brightness # LED off

If it was possible to control the LED, I wondered, could the LCD display be controlled too? That turned into a fun reverse-engineering exercise I’m documenting here for others to build on.

What’s the display?

This Ubiquiti forum post contained another command to turn off the LCD display, which gives a first clue:

echo 0  > /sys/class/backlight/fb_sp8110/brightness

What is fb_sp8110? It seems specific to the LCD. When Googling it, I only found other Ubiquiti forum posts:

Google search for fb_sp8110

The other two results provide the next clue: logs containing error messages from fb_sp8110 that reference an interestingly named fbtftupdatedisplay module.

fb_sp8110 spi3.0: fbtft_update_display: <some error>

I Googled fbtft_update_display and formed a clearer picture:

  • fbtft is a Linux framebuffer driver for SPI TFT screens
  • TFT stands for “thin-film-transistor”, a specific variant of LCD screen
  • SPI stands for ”serial peripheral interface”, a communication protocol
  • SPI TFT displays are common in hobbyist projects (ex. Raspberry Pi)

How can we control it?

This is a good start - let’s do some more basic enumeration with this new information.

If you search the kernel logs for tft, you can find specifications of the display:

root@cloudkey:/sys/class# dmesg | grep -i tft
[    0.819476] fbtft_of_value: width = 160
[    0.819479] fbtft_of_value: height = 60
[    0.819483] fbtft_of_value: buswidth = 8
[    0.819486] fbtft_of_value: bpp = 16
[    0.819490] fbtft_of_value: rotate = 180
[    0.819494] fbtft_of_value: fps = 100

And if you search the kernel logs for graphics, you can find the block device that represents the display in the filesystem (fb0):

$ dmesg | grep graphics
[    0.945633] graphics fb0: fb_sp8110 frame buffer, 160x60, 18 KiB video memory, 16 KiB buffer memory, fps=100, spi3.0 at 19 MHz

Writing to this block device should cause something to happen on the display. As a quick test, we can write random bytes to the frame buffer:

cp /dev/urandom /dev/fb0

Ta-da! A display full of (our) static.

Similarly, you can clear / blank the display with:

cp /dev/null /dev/fb0

Persisting the Screen

In a few seconds, the static disappears as the original UI returns. It’s periodically redrawn by the UniFi software. If the aim is to replace the screen with our own information, we probably don’t want to fight for control of the framebuffer.

We can use lsof to find which process ID is drawing the UniFi UI and then disable it. The process begins again after a device reboot.

root@cloudkey:~# lsof /dev/fb0
ck-ui   685 root  mem    CHR   29,0          4441 /dev/fb0
ck-ui   685 root    4u   CHR   29,0      0t0 4441 /dev/fb0	

root@cloudkey:~# pgrep ck-ui

root@cloudkey:~# kill 685

root@cloudkey:~# watch pgrep ck-ui

Drawing Arbitrary Data

A post on wavesharejfs’s blog has some starter code for driving these displays with Python. It leverages the Python Imaging Library (“PIL” or later “Pillow”):

#!/usr/bin/env python2
import os
import struct
from PIL import Image

im ='test.bmp')

w, h = im.size

with open('temp.fb', 'wb') as f:
    for j in range(0,h):
        for i in range(0,w):
            r,g,b =im.getpixel((i,j))
            rgb=struct.pack('H',((r >> 3) << 11)|((j >> 2) << 5)|(b >> 3))
os.system('cat temp.fb > /dev/fb0')

You can use this to feed 160x60 pixel images into the display. You can use ImageMagick’s convert tool to shrink images on the command line.

Demo of Cloud Key screen control

From here, you could use matplotlib to generate custom infographics about your network(s) fed by the controller’s API, and so on.

I'm a builder inspiring others one project at a time.

Learn more and reach out on LinkedIn.

© 2021 Dalton Flanagan