{ "info": { "author": "k0rventen", "author_email": "", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3" ], "description": "# Control of an Elgato Avea bulb using Python\n\n[![PyPI](https://img.shields.io/pypi/v/avea.svg)](https://pypi.org/project/avea/)\n[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/k0rventen/avea.svg?)](https://lgtm.com/projects/g/k0rventen/avea/context:python)\n[![Build Status](https://travis-ci.com/k0rventen/avea.svg?branch=master)](https://travis-ci.com/k0rventen/avea)\n\n\nThe [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.\n\nThis project aim to control it using a Bluetooth 4.0 compatible device and some Python magic.\n\nTested on Raspberry Pi 3 and Zero W (with integrated bluetooth). \n\n- [Control of an Elgato Avea bulb using Python](#control-of-an-elgato-avea-bulb-using-python)\n - [TL;DR](#tldr)\n - [Library usage](#library-usage)\n - [Code documentation](#code-documentation)\n - [Reverse engineering of the bulb](#reverse-engineering-of-the-bulb)\n - [Communication protocol](#communication-protocol)\n - [Intro](#intro)\n - [Commands and payload explanation](#commands-and-payload-explanation)\n - [Color command](#color-command)\n - [Brightness command](#brightness-command)\n - [Walkthrough & Example](#walkthrough--example)\n - [Brightness](#brightness)\n - [Color](#color)\n - [Python implementation](#python-implementation)\n - [One-liner for color computation](#one-liner-for-color-computation)\n - [Bluepy writeCharacteristic() overwrite](#bluepy-writecharacteristic-overwrite)\n - [Working with notifications using Bluepy](#working-with-notifications-using-bluepy)\n - [TODO](#todo)\n\n## TL;DR\n\nThe lib requires [bluepy](https://github.com/IanHarvey/bluepy), so we must install the following dependancy, wheter we use pip or install from source.\n\n**Dependancies**\n\n```\nsudo apt install libglib2.0-dev\n```\n\n**Then install from pip3**\n\n```bash\nsudo apt install python3-pip\nsudo pip3 install --upgrade avea\n```\n\n**or if you prefer installing from source**\n\n```bash\ngit clone https://github.com/k0rventen/avea\ncd avea\nsudo python3 setup.py install\n```\n\n## Library usage\n\nYou can check the example script `example.py`, to try it directly onto your bulbs :\n\n```bash\nsudo python3 example.py\n```\n\nBelow is a quick how-to of the various methods of the library.\n\n**Note : the discover\\_avea\\_bulbs() function needs root privileges, due to bluepy's scan(). From your user, you can use sudo -E.**\n\n```python\nimport avea # Important !\n\n# Get nearby bulbs in a list, then retrieve the name of all bulbs\n# using this method requires root privileges (because of bluepy's scan() )\nnearbyBulbs = avea.discover_avea_bulbs()\nfor bulb in nearbyBulbs:\n bulb.get_name()\n print(bulb.name)\n\n# Or create a bulb if you know its address (after a scan for example)\nmyBulb = avea.Bulb(\"xx:xx:xx:xx:xx:xx\")\n\n# You can set the brightness, color and name\nmyBulb.set_brightness(2000) # ranges from 0 to 4095\nmyBulb.set_color(0,4095,0,0) # in order : white, red, green, blue\nmyBulb.set_rgb(0,255,0) # RGB compliant function\nmyBulb.set_smooth_transition(255,255,0,4,30) # change to rgb(255,255,0) in 4s with 30 iterations per second\nmyBulb.set_name(\"bedroom\") # new name of the bulb\n\n# And get the brightness, color and name\nprint(myBulb.get_name()) # Query the name of the bulb\ntheColor = myBulb.get_color() # Query the current color\ntheRgbColor = myBulb.get_rgb() # Query the bulb in a RGB format\ntheBrightness = myBulb.get_brightness() # query the current brightness\ntheAddr = myBulb.addr # query the bulb Bluetooth addr\ntheFwVersion = myBulb.get_fw_version() # query the bulb firmware version\n```\n\nThat's it. Pretty simple.\n\nCheck the explanations below for more informations, or check the sources !\n\n\n## Code documentation\n\n## Reverse engineering of the bulb\n\nI'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.\n\nBelow is a pretty thorough explanation of the BLE communication and the python implementation to communicate with the bulb.\n\nAs 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.\n\n\n## Communication protocol\n\n### Intro\n\nTo 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).\n\nTo sum up, the bulb emits a set of `services` which have `characteristics`. We use the latter to communicate to the device.\n\nThe 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.\n\n### Commands and payload explanation\n\nThe first bytes of transmission is the command. A few commands are available :\n\nValue | Command\n--- | ---\n0x35 | set / get bulb color\n0x57 | set / get bulb brightness\n0x58 | set / get bulb name\n\n### Color command\n\nFor the color command, the transmission payload is as follows :\n\nCommand | Fading time | Useless byte | White value | Red value | Green value | Blue value\n---|---|---|---|---|---|---\n\nEach value of the payload is a 4 hexadecimal value. (The actual values are integers between 0 and 4095)\n\nFor each color, a prefix in the hexadecimal value is needed :\n\nColor | prefix\n---|---\nWhite| 0x8000\nRed | 0x3000\nGreen | 0x2000\nBlue | 0X1000\n\nThe values are then formatted in **big-endian** format :\n\nInt | 4-bytes Hexadecimal | Big-endian hex\n---|---|---\n4095 | 0x0fff| **0xff0f**\n\n### Brightness command\n\nThe brightness is also an Int value between 0 and 4095, sent as a big-endian 4-bytes hex value. The transmission looks like this :\n\nCommand | Brightness value |\n---|---\n0x57 | 0xff00\n\n## Walkthrough & Example\n\nLet say we want the bulb to be pink at 75% brightness :\n\n### Brightness\n\n75% brightness is roughly 3072 (out of the maximum 4095):\n\nInt | 4-bytes Hexadecimal | **Big-endian hex**\n---|---|---\n3072 |0x0C00| **0x000C**\n\nThe brightness command will be `0x57000C`\n\n#### Color\n\nPink 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 :\n\nVariables | Int Values | Hexadecimal values | Bitwise XOR | Big-endian values\n---|---|---|---|---\nWhite| 0| 0x0000| 0x8000| 0x0080\nRed | 4095| 0x0fff| 0x3fff| 0xff3f\nGreen | 0 | 0x0000| 0x2000 | 0x0020\nBlue | 4095| 0x0fff | 0x1fff| 0xff1f\n\n\nThe final byte sequence for a pink bulb will be :\n\nCommand | Fading time | Useless byte | White value | Red value | Green value | Blue value\n---|---|---|---|---|---|---\n`0x35`|`1101`| `0000`| `0080`|`ff3f`|`0020`|`ff1f`\n\n\n## Python implementation\nBelow is some python3 code regarding various aspects that are quite interesting.\n\n### One-liner for color computation\nTo compute the correct values for each color, I created the following conversion (here showing for white) :\n\n```python\nwhite = (int() | int(0x8000)).to_bytes(2, byteorder='little').hex()\n```\n\n### Bluepy writeCharacteristic() overwrite\nBy 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 :\n\n```python\nclass AveaPeripheral(bluepy.btle.Peripheral):\n def writeCharacteristic(self, handle, val, withResponse=True):\n cmd = \"wrr\" if withResponse else \"wr\"\n self._writeCmd(\"%s %X %s\\n\" % (cmd, handle, val))\n return self._getResp('wr')\n```\n\n### Working with notifications using Bluepy\nTo reply to our packets, the bulb is using BLE notifications, and some setup is required to be able to receive these notifications with bluepy.\n\nTo 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)\n\n```python\nself.bulb.writeCharacteristic(41, \"0100\")\n```\nAfter that, we will receive notifications from the bulb.\n\n## TODO\n- Reverse engineer the `ambiances` (which are mood-based scenes).\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/k0rventen/avea", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "avea", "package_url": "https://pypi.org/project/avea/", "platform": "", "project_url": "https://pypi.org/project/avea/", "project_urls": { "Homepage": "https://github.com/k0rventen/avea" }, "release_url": "https://pypi.org/project/avea/1.5.2/", "requires_dist": [ "bluepy" ], "requires_python": "", "summary": "Control an Elgato Avea bulb using python3", "version": "1.5.2", "yanked": false, "yanked_reason": null }, "last_serial": 9098120, "releases": { "1.2.6": [ { "comment_text": "", "digests": { "md5": "9f11e6fa164a33c690905e808242c8ab", "sha256": "81e95918f39833f07c35f58b65e0f0a63499aeed706da1d3d1162512179a2bf5" }, "downloads": -1, "filename": "avea-1.2.6-py3-none-any.whl", "has_sig": false, "md5_digest": "9f11e6fa164a33c690905e808242c8ab", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 8404, "upload_time": "2019-04-15T08:09:05", "upload_time_iso_8601": "2019-04-15T08:09:05.489995Z", "url": "https://files.pythonhosted.org/packages/19/1b/d0f145ebdd500f5ce5b7706d0b0a866c4d4737f9ce46c151a411bf05a7c1/avea-1.2.6-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "136967b6b437b51557aaddad43c55921", "sha256": "e5bff41e76912b3a8dcee825ff24355f89376c0aba7e97bfffa50d9ba512035e" }, "downloads": -1, "filename": "avea-1.2.6.tar.gz", "has_sig": false, "md5_digest": "136967b6b437b51557aaddad43c55921", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8397, "upload_time": "2019-04-15T08:09:08", "upload_time_iso_8601": "2019-04-15T08:09:08.060964Z", "url": "https://files.pythonhosted.org/packages/57/bd/49ec4c084d14d6c94b5e6c6a98817764977ca944f7a6bed3390bf5e57cd5/avea-1.2.6.tar.gz", "yanked": false, "yanked_reason": null } ], "1.2.7": [ { "comment_text": "", "digests": { "md5": "419602b0459cad733b27e2cd355b35ba", "sha256": "20083a6e3b1a20855404f45bf781aecc0055922fef58704f146c97b48cf3deb1" }, "downloads": -1, "filename": "avea-1.2.7-py3-none-any.whl", "has_sig": false, "md5_digest": "419602b0459cad733b27e2cd355b35ba", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 8409, "upload_time": "2019-04-24T06:57:25", "upload_time_iso_8601": "2019-04-24T06:57:25.834806Z", "url": "https://files.pythonhosted.org/packages/d3/52/c57f7ddec3d76eda181866540caaa23304adff6758e0b16962199e0aa6f4/avea-1.2.7-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "503202c2050b5f6df9509bde2f6bf495", "sha256": "a53b426ba51e5a55cbe7a5c0cd9247e5ae6702cdb301502aa3e66ac8d1dee29d" }, "downloads": -1, "filename": "avea-1.2.7.tar.gz", "has_sig": false, "md5_digest": "503202c2050b5f6df9509bde2f6bf495", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8387, "upload_time": "2019-04-24T06:57:27", "upload_time_iso_8601": "2019-04-24T06:57:27.220850Z", "url": "https://files.pythonhosted.org/packages/7d/9a/e924ef367ae15499797064327403f37811c422277c36be6e0088bcaa3d3f/avea-1.2.7.tar.gz", "yanked": false, "yanked_reason": null } ], "1.2.8": [ { "comment_text": "", "digests": { "md5": "aef4f39810f2b7376ab9749d0af2d997", "sha256": "d5bea97e76cfa752fa9bab2e18650ed91ab7120ab48023ce95a8af6fda64536f" }, "downloads": -1, "filename": "avea-1.2.8-py3-none-any.whl", "has_sig": false, "md5_digest": "aef4f39810f2b7376ab9749d0af2d997", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 8409, "upload_time": "2019-05-20T19:58:52", "upload_time_iso_8601": "2019-05-20T19:58:52.962793Z", "url": "https://files.pythonhosted.org/packages/fd/28/d32f3f6116eaa371acf70179f4e7e52bbc3bea1e8bc4128cccb373dbff08/avea-1.2.8-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "562f35264054fecbe245865885f10383", "sha256": "1edf14d578a36043675ac0cbc23a459a9e9ae023762ca654ed165a3a9815b800" }, "downloads": -1, "filename": "avea-1.2.8.tar.gz", "has_sig": false, "md5_digest": "562f35264054fecbe245865885f10383", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8398, "upload_time": "2019-05-20T19:58:54", "upload_time_iso_8601": "2019-05-20T19:58:54.837712Z", "url": "https://files.pythonhosted.org/packages/c1/e9/9542bc8fe69b8a34e6a74f2b73eb8d4bbffaf8b3e1c8d413a3fd9cf2569d/avea-1.2.8.tar.gz", "yanked": false, "yanked_reason": null } ], "1.4": [ { "comment_text": "", "digests": { "md5": "60287a2a4e5c058772e5d9685cecc4a5", "sha256": "efc7f105104b42e2b248d9c8c4b69821c2cc550786b8e7118a2a7cb613a78690" }, "downloads": -1, "filename": "avea-1.4-py3-none-any.whl", "has_sig": false, "md5_digest": "60287a2a4e5c058772e5d9685cecc4a5", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 9388, "upload_time": "2019-10-26T20:22:54", "upload_time_iso_8601": "2019-10-26T20:22:54.372224Z", "url": "https://files.pythonhosted.org/packages/14/e5/d1217fe9c17939d7da6bc1fb933dabb1f7d06ef1a3b342a8df2c7fe01324/avea-1.4-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "046b1dedc70ad5db5b970cf429766ff3", "sha256": "acfded1e38fcb11619b31b0858b92895465a1f6ce8ae77a39aed70867003c363" }, "downloads": -1, "filename": "avea-1.4.tar.gz", "has_sig": false, "md5_digest": "046b1dedc70ad5db5b970cf429766ff3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 9399, "upload_time": "2019-10-26T20:22:56", "upload_time_iso_8601": "2019-10-26T20:22:56.163916Z", "url": "https://files.pythonhosted.org/packages/d2/3a/e9b2bbf3b16fa12c23f02f01df1e7b7c0e0f24a2587ec13977934ec4db47/avea-1.4.tar.gz", "yanked": false, "yanked_reason": null } ], "1.5.1": [ { "comment_text": "", "digests": { "md5": "07a13fc311aa8d5ac6d4b2fb9f47f878", "sha256": "d8cb6243d19094b16bd54a9320fc3d5bfdb7973cd7e2d3a1dbd122a66eac7000" }, "downloads": -1, "filename": "avea-1.5.1-py3-none-any.whl", "has_sig": false, "md5_digest": "07a13fc311aa8d5ac6d4b2fb9f47f878", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 9714, "upload_time": "2020-11-24T20:55:59", "upload_time_iso_8601": "2020-11-24T20:55:59.948050Z", "url": "https://files.pythonhosted.org/packages/88/3a/e834216f590cff28325540f114fe382668d445c19b42a4b30aeeee81636a/avea-1.5.1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "7cc444bc79ca3d76d666293dd470ccd8", "sha256": "0231058e1b0c73ac08835939f7be40ad214b7bad28728ad52a6ba62ad0fa767b" }, "downloads": -1, "filename": "avea-1.5.1.tar.gz", "has_sig": false, "md5_digest": "7cc444bc79ca3d76d666293dd470ccd8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11437, "upload_time": "2020-11-24T20:56:01", "upload_time_iso_8601": "2020-11-24T20:56:01.006823Z", "url": "https://files.pythonhosted.org/packages/ff/f3/48ccb775dc0ceee88222d68bf8c5cfd7e9687201c47d5328c07e65e238eb/avea-1.5.1.tar.gz", "yanked": false, "yanked_reason": null } ], "1.5.2": [ { "comment_text": "", "digests": { "md5": "3e18e97a53d6a94a10a4ed0efe87ec6f", "sha256": "f9bddedf9d6632a878faa01117b91ec86b057d02c96fcbc1cea47299175c537d" }, "downloads": -1, "filename": "avea-1.5.2-py3-none-any.whl", "has_sig": false, "md5_digest": "3e18e97a53d6a94a10a4ed0efe87ec6f", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 9734, "upload_time": "2021-01-10T15:39:33", "upload_time_iso_8601": "2021-01-10T15:39:33.305130Z", "url": "https://files.pythonhosted.org/packages/59/28/98ebdadaf295280e1e4e185575c2e52ee5551000a7bdca50639cabc34070/avea-1.5.2-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "835554f57b3b18c1344ec89bab7e266a", "sha256": "417ba45e63029a593538f9ea94a562177d5d89ca8f7a3ed6a4f3325ce0aeb564" }, "downloads": -1, "filename": "avea-1.5.2.tar.gz", "has_sig": false, "md5_digest": "835554f57b3b18c1344ec89bab7e266a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12146, "upload_time": "2021-01-10T15:39:34", "upload_time_iso_8601": "2021-01-10T15:39:34.340369Z", "url": "https://files.pythonhosted.org/packages/0f/0e/cd6e62e9443ebed417e608394f34c2ae4a3d55f30a80048c4a67d8a95010/avea-1.5.2.tar.gz", "yanked": false, "yanked_reason": null } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "3e18e97a53d6a94a10a4ed0efe87ec6f", "sha256": "f9bddedf9d6632a878faa01117b91ec86b057d02c96fcbc1cea47299175c537d" }, "downloads": -1, "filename": "avea-1.5.2-py3-none-any.whl", "has_sig": false, "md5_digest": "3e18e97a53d6a94a10a4ed0efe87ec6f", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 9734, "upload_time": "2021-01-10T15:39:33", "upload_time_iso_8601": "2021-01-10T15:39:33.305130Z", "url": "https://files.pythonhosted.org/packages/59/28/98ebdadaf295280e1e4e185575c2e52ee5551000a7bdca50639cabc34070/avea-1.5.2-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "835554f57b3b18c1344ec89bab7e266a", "sha256": "417ba45e63029a593538f9ea94a562177d5d89ca8f7a3ed6a4f3325ce0aeb564" }, "downloads": -1, "filename": "avea-1.5.2.tar.gz", "has_sig": false, "md5_digest": "835554f57b3b18c1344ec89bab7e266a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12146, "upload_time": "2021-01-10T15:39:34", "upload_time_iso_8601": "2021-01-10T15:39:34.340369Z", "url": "https://files.pythonhosted.org/packages/0f/0e/cd6e62e9443ebed417e608394f34c2ae4a3d55f30a80048c4a67d8a95010/avea-1.5.2.tar.gz", "yanked": false, "yanked_reason": null } ], "vulnerabilities": [] }