Skip to main content

Control an Elgato Avea bulb using python

Project description

Control of an Elgato Avea bulb using Python

PyPI Language grade: Python Build Status

The Avea bulb from Elgato is a light bulb that connects to an iPhone or Android app via Bluetooth.

This project aim to control it using a Bluetooth 4.0 compatible device and some Python magic.

Tested on Raspberry Pi 3 and Zero W (with integrated bluetooth).

TL;DR

The lib requires bluepy, so we must install the following dependancy, wheter we use pip or install from source.

Dependancies

sudo apt install libglib2.0-dev

Then install from pip3

sudo apt install python3-pip
sudo pip3 install --upgrade avea

or if you prefer installing from source

git clone https://github.com/k0rventen/avea
cd avea
sudo python3 setup.py install

Library usage

Below is a quick how-to of the various methods of the library.

Note : the discover_avea_bulbs() function needs root privileges, due to bluepy's scan(). From your user, you can use sudo -E.

import avea # Important !

# Get nearby bulbs in a list, then retrieve the name of all bulbs
# using this method requires root privileges (because of bluepy's scan() )
nearbyBulbs = avea.discover_avea_bulbs()
for bulb in nearbyBulbs:
    bulb.get_name()
    print(bulb.name)

# Or create a bulb if you know its address
myBulb = avea.Bulb("xx:xx:xx:xx:xx:xx")

# You can set the brightness, color and name
myBulb.set_brightness(2000)  # ranges from 0 to 4095
myBulb.set_color(0,4095,0,0)  # in order : white, red, green, blue
myBulb.set_rgb(0,255,0) # RGB compliant function
myBulb.set_name("bedroom")

# And get the bulb brightness, color and name
print(myBulb.get_name())  # Query the name of the bulb
theColor = myBulb.get_color() # Query the current color
theRgbColor = myBulb.get_rgb() # Query the bulb in a RGB format
theBrightness = myBulb.get_brightness() # query the current brightness

That's it. Pretty simple.

Check the explanations below for more informations, or check the sources !

Reverse engineering of the bulb

I've used the informations given by Marmelatze as well as some reverse engineering using a btsnoop_hci.log file and Wireshark.

Below is a pretty thorough explanation of the BLE communication and the python implementation to communicate with the bulb.

As BLE communication is quite complicated, you might want to skip all of this if you just want to use the library. But it's quite interesting.

Communication protocol

Intro

To communicate the bulb uses Bluetooth 4.0 "BLE", which provide some interesting features for communications, to learn more about it go here.

To sum up, the bulb emits a set of services which have characteristics. We use the latter to communicate to the device.

The bulb uses the service f815e810456c6761746f4d756e696368 and the associated characteristic f815e811456c6761746f4d756e696368 to send and receive informations about its state (color, name and brightness)

Commands and payload explanation

The first bytes of transmission is the command. A few commands are available :

Value Command
0x35 set / get bulb color
0x57 set / get bulb brightness
0x58 set / get bulb name

Color command

For the color command, the transmission payload is as follows :

Command Fading time Useless byte White value Red value Green value Blue value

Each value of the payload is a 4 hexadecimal value. (The actual values are integers between 0 and 4095)

For each color, a prefix in the hexadecimal value is needed :

Color prefix
White 0x8000
Red 0x3000
Green 0x2000
Blue 0X1000

The values are then formatted in big-endian format :

Int 4-bytes Hexadecimal Big-endian hex
4095 0x0fff 0xff0f

Brightness command

The brightness is also an Int value between 0 and 4095, sent as a big-endian 4-bytes hex value. The transmission looks like this :

Command Brightness value
0x57 0xff00

Walkthrough & Example

Let say we want the bulb to be pink at 75% brightness :

Brightness

75% brightness is roughly 3072 (out of the maximum 4095):

Int 4-bytes Hexadecimal Big-endian hex
3072 0x0C00 0x000C

The brightness command will be 0x57000C

Color

Pink is 100% red, 100% blue, no green. (We assume that the white value is also 0.) For each color, we convert the int value to hexadecimal, then we apply the prefix, then we convert to big-endian :

Variables Int Values Hexadecimal values Bitwise XOR Big-endian values
White 0 0x0000 0x8000 0x0080
Red 4095 0x0fff 0x3fff 0xff3f
Green 0 0x0000 0x2000 0x0020
Blue 4095 0x0fff 0x1fff 0xff1f

The final byte sequence for a pink bulb will be :

Command Fading time Useless byte White value Red value Green value Blue value
0x35 1101 0000 0080 ff3f 0020 ff1f

Python implementation

Below is some python3 code regarding various aspects that are quite interesting.

One-liner for color computation

To compute the correct values for each color, I created the following conversion (here showing for white) :

white = (int(<value>) | int(0x8000)).to_bytes(2, byteorder='little').hex()

Bluepy writeCharacteristic() overwrite

By default, the btle.Peripheral() object of bluepy only allows to send UTF-8 encoded strings, which are internally converted to hexadecimal. As we craft our own hexadecimal payload, we need to bypass this behavior. A child class of Peripheral() is created and overwrites the writeCharacteristic() method, as follows :

class AveaPeripheral(bluepy.btle.Peripheral):
    def writeCharacteristic(self, handle, val, withResponse=True):
        cmd = "wrr" if withResponse else "wr"
        self._writeCmd("%s %X %s\n" % (cmd, handle, val))
        return self._getResp('wr')

Working with notifications using Bluepy

To reply to our packets, the bulb is using BLE notifications, and some setup is required to be able to receive these notifications with bluepy.

To subscribe to the bulb's notifications, we must send a "0100" to the BLE handle which is just after the one used for communication. As we use handle 0x0028 (40 for bluepy) to communicate, we will send the notification payload to the handle 0x0029 (so 41 for bluepy)

self.bulb.writeCharacteristic(41, "0100")

After that, we will receive notifications from the bulb.

TODO

  • Reverse engineer and the ambiances (which are mood-based scenes).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

avea-1.2.6.tar.gz (8.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

avea-1.2.6-py3-none-any.whl (8.4 kB view details)

Uploaded Python 3

File details

Details for the file avea-1.2.6.tar.gz.

File metadata

  • Download URL: avea-1.2.6.tar.gz
  • Upload date:
  • Size: 8.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.12.1 pkginfo/1.5.0.1 requests/2.21.0 setuptools/40.8.0 requests-toolbelt/0.8.0 tqdm/4.30.0 CPython/3.7.3

File hashes

Hashes for avea-1.2.6.tar.gz
Algorithm Hash digest
SHA256 e5bff41e76912b3a8dcee825ff24355f89376c0aba7e97bfffa50d9ba512035e
MD5 136967b6b437b51557aaddad43c55921
BLAKE2b-256 57bd49ec4c084d14d6c94b5e6c6a98817764977ca944f7a6bed3390bf5e57cd5

See more details on using hashes here.

File details

Details for the file avea-1.2.6-py3-none-any.whl.

File metadata

  • Download URL: avea-1.2.6-py3-none-any.whl
  • Upload date:
  • Size: 8.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.12.1 pkginfo/1.5.0.1 requests/2.21.0 setuptools/40.8.0 requests-toolbelt/0.8.0 tqdm/4.30.0 CPython/3.7.3

File hashes

Hashes for avea-1.2.6-py3-none-any.whl
Algorithm Hash digest
SHA256 81e95918f39833f07c35f58b65e0f0a63499aeed706da1d3d1162512179a2bf5
MD5 9f11e6fa164a33c690905e808242c8ab
BLAKE2b-256 191bd0f145ebdd500f5ce5b7706d0b0a866c4d4737f9ce46c151a411bf05a7c1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page