Package: avea
Control of an Elgato Avea bulb using PythonTheAvea bulb from Elgatois 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 PythonTL;DRLibrary usageCode documentationReverse engineering of the bulbCommunication protocolIntroCommands and payload explanationColor commandBrightness commandWalkthrough & ExampleBrightnessColorPython implementationOne-liner for color computationBluepy writeCharacteristic() overwriteWorking with notifications using BluepyTODOTL;DRThe lib requiresbluepy, so we must install the following dependancy, wheter we use pip or install from source.Dependanciessudo apt install libglib2.0-devThen install from pip3sudoaptinstallpython3-pip
sudopip3install--upgradeaveaor if you prefer installing from sourcegitclonehttps://github.com/k0rventen/aveacdavea
sudopython3setup.pyinstallLibrary usageYou can check the example scriptexample.py, to try it directly onto your bulbs :sudopython3example.pyBelow 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.importavea# 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()forbulbinnearbyBulbs: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 namemyBulb.set_brightness(2000)# ranges from 0 to 4095myBulb.set_color(0,4095,0,0)# in order : white, red, green, bluemyBulb.set_rgb(0,255,0)# RGB compliant functionmyBulb.set_smooth_transition(255,255,0,4,30)# change to rgb(255,255,0) in 4s with 30 iterations per secondmyBulb.set_name("bedroom")# new name of the bulb# And get the brightness, color and nameprint(myBulb.get_name())# Query the name of the bulbtheColor=myBulb.get_color()# Query the current colortheRgbColor=myBulb.get_rgb()# Query the bulb in a RGB formattheBrightness=myBulb.get_brightness()# query the current brightnesstheAddr=myBulb.addr# query the bulb Bluetooth addrtheFwVersion=myBulb.get_fw_version()# query the bulb firmware versionThat's it. Pretty simple.Check the explanations below for more informations, or check the sources !Code documentationReverse engineering of the bulbI've used the informations given byMarmelatzeas well as some reverse engineering using abtsnoop_hci.logfile 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 protocolIntroTo communicate the bulb uses Bluetooth 4.0 "BLE", which provide some interesting features for communications, to learn more about it gohere.To sum up, the bulb emits a set ofserviceswhich havecharacteristics. We use the latter to communicate to the device.The bulb uses the servicef815e810456c6761746f4d756e696368and the associated characteristicf815e811456c6761746f4d756e696368to send and receive informations about its state (color, name and brightness). We'll transmit over this characteristic.Commands and payload explanationThe first bytes of transmission is the command. A few commands are available :ValueCommand0x35set / get bulb color0x57set / get bulb brightness0x58set / get bulb nameColor commandFor the color command, the transmission payload is as follows :CommandFading timeUseless byteWhite valueRed valueGreen valueBlue valueEach 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 :ColorprefixWhite0x8000Red0x3000Green0x2000Blue0X1000The values are then formatted inbig-endianformat :Int4-bytes HexadecimalBig-endian hex40950x0fff0xff0fBrightness commandThe brightness is also an Int value between 0 and 4095, sent as a big-endian 4-bytes hex value. The transmission looks like this :CommandBrightness value0x570xff00Walkthrough & ExampleLet say we want the bulb to be pink at 75% brightness :Brightness75% brightness is roughly 3072 (out of the maximum 4095):Int4-bytes HexadecimalBig-endian hex30720x0C000x000CThe brightness command will be0x57000CColorPink 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 :VariablesInt ValuesHexadecimal valuesBitwise XORBig-endian valuesWhite00x00000x80000x0080Red40950x0fff0x3fff0xff3fGreen00x00000x20000x0020Blue40950x0fff0x1fff0xff1fThe final byte sequence for a pink bulb will be :CommandFading timeUseless byteWhite valueRed valueGreen valueBlue value0x35110100000080ff3f0020ff1fPython implementationBelow is some python3 code regarding various aspects that are quite interesting.One-liner for color computationTo 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() overwriteBy 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 :classAveaPeripheral(bluepy.btle.Peripheral):defwriteCharacteristic(self,handle,val,withResponse=True):cmd="wrr"ifwithResponseelse"wr"self._writeCmd("%s%X%s\n"%(cmd,handle,val))returnself._getResp('wr')Working with notifications using BluepyTo 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.TODOReverse engineer theambiances(which are mood-based scenes).