Skip to main content

Control of an Elgato Avea bulb using Python

PyPI CI Validation Python Versions License

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 bleak, so we must install the following dependency, whether we use pip or install from source.

Dependencies

Ensure your system Bluetooth stack is available (for example bluez on Linux or the built-in CoreBluetooth framework on macOS). The Python dependency bleak is installed automatically when using pip.

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

You can check the example script example.py, to try it directly onto your bulbs :

sudo python3 example.py

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

Note : depending on your operating system configuration, Bluetooth discovery may require additional permissions (for example running with sudo on Linux or granting Bluetooth access on macOS).

import avea # Important !

# Get nearby bulbs in a list, then retrieve the name of all bulbs
# discovery might require elevated privileges depending on the platform
nearbyBulbs = avea.discover_avea_bulbs()
for bulb in nearbyBulbs:
    bulb.get_name()
    print(bulb.name)

# Or create a bulb if you know its address (after a scan for example)
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_smooth_transition(255,255,0,4,30)   # change to rgb(255,255,0) in 4s with 30 iterations per second
myBulb.set_name("bedroom")                  # new name of the bulb

# And get the 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
theAddr = myBulb.addr                   # query the bulb Bluetooth addr
theFwVersion = myBulb.get_fw_version()  # query the bulb firmware version
theSerialNumber = myBulb.get_serial_number()  # query the bulb serial number
theHardwareRevision = myBulb.get_hardware_revision()  # query the bulb hardware revision
theManufacturerName = myBulb.get_manufacturer_name()  # query the bulb manufacturer name

That's it. Pretty simple.

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

Code documentation

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 from an Android device 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). We'll transmit over this characteristic.

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()

Bleak write_gatt_char usage

Bleak lets us send raw binary payloads straight to a characteristic without any extra overrides. The library now prepares the payload as bytes and calls the client directly:

await client.write_gatt_char(CONTROL_CHARACTERISTIC_UUID, payload, response=False)

Working with notifications using Bleak

Notifications are enabled through BleakClient.start_notify. During the connection phase the library subscribes to the Avea control characteristic and routes every notification to a callback that updates the cached bulb state. Synchronous getters wait on an asyncio.Event that is set when the expected command is received, keeping the public API blocking while leveraging bleak under the hood.

TODO

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

Release files for avea 1.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for avea 1.8.0
File Size Uploaded
avea-1.8.0.tar.gz 15.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for avea 1.8.0
File Interpreter ABI Platform
avea-1.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 26.3 kB

Release files / avea-1.8.0.tar.gz

Download URL avea-1.8.0.tar.gz
Size 15.4 kB
Tags Source
SHA-256 checksum
How to use checksums
3a40e68a6ff38f4026928d05193a0fbdfcf5d5eb51a4533c2430cbd126c74cef
BLAKE2b-256 checksum
How to use checksums
3589dbfafc10d48e437bc85c08bfa80ea6cb7e1a0d5c9b655022028e100fac82
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.5

Release files / avea-1.8.0-py3-none-any.whl

Download URL avea-1.8.0-py3-none-any.whl
Size 10.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a0803ccea975766842823fccdc7105a8b080288595c146f2e27bfdc1d6934fca
BLAKE2b-256 checksum
How to use checksums
00a4c3ab7e452ca651c60f4b439d9c9548eef0de76c5bd903b84d01391f45437
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.5

Release history Release notifications | RSS feed

This release

1.8.0 This release

2 release files

1.7.0

2 release files

1.6.1

2 release files

1.6.0

2 release files

1.5.2

2 release files

1.5.1

2 release files

1.4

2 release files

1.2.8

2 release files

1.2.7

2 release files

1.2.6

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page