Metadata-Version: 2.4 Name: avea Version: 1.8.0 Summary: Control an Elgato Avea bulb using python3 Home-page: https://github.com/k0rventen/avea Author: k0rventen License: MIT Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX :: Linux Requires-Python: >=3.10,<4 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: bleak>=1.0.0 Requires-Dist: bleak-retry-connector>=4.0.0 Dynamic: author Dynamic: classifier Dynamic: description Dynamic: description-content-type Dynamic: home-page Dynamic: license Dynamic: license-file Dynamic: requires-dist Dynamic: requires-python Dynamic: summary # Control of an Elgato Avea bulb using Python [![PyPI](https://img.shields.io/pypi/v/avea.svg)](https://pypi.org/project/avea/) [![CI Validation](https://github.com/k0rventen/avea/actions/workflows/ci-validation.yml/badge.svg)](https://github.com/k0rventen/avea/actions/workflows/ci-validation.yml) [![Python Versions](https://img.shields.io/pypi/pyversions/avea.svg)](https://pypi.org/project/avea/) [![License](https://img.shields.io/pypi/l/avea.svg)](https://github.com/k0rventen/avea/blob/master/LICENSE) The [Avea bulb from Elgato](https://www.amazon.co.uk/Elgato-Avea-Dynamic-Light-Android-Smartphone/dp/B00O4EZ11Q) 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). - [Control of an Elgato Avea bulb using Python](#control-of-an-elgato-avea-bulb-using-python) - [TL;DR](#tldr) - [Library usage](#library-usage) - [Code documentation](#code-documentation) - [Reverse engineering of the bulb](#reverse-engineering-of-the-bulb) - [Communication protocol](#communication-protocol) - [Intro](#intro) - [Commands and payload explanation](#commands-and-payload-explanation) - [Color command](#color-command) - [Brightness command](#brightness-command) - [Walkthrough & Example](#walkthrough--example) - [Brightness](#brightness) - [Color](#color) - [Python implementation](#python-implementation) - [One-liner for color computation](#one-liner-for-color-computation) - [Bleak write\_gatt\_char usage](#bleak-write_gatt_char-usage) - [Working with notifications using Bleak](#working-with-notifications-using-bleak) - [TODO](#todo) ## TL;DR The lib requires [bleak](https://github.com/hbldh/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** ```bash sudo apt install python3-pip sudo pip3 install --upgrade avea ``` **or if you prefer installing from source** ```bash 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 : ```bash 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).** ```python 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](https://github.com/Marmelatze/avea_bulb) 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](https://learn.adafruit.com/introduction-to-bluetooth-low-energy/gatt). 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) : ```python white = (int() | 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: ```python 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).