k0rventen/avea
GitHub: k0rventen/avea
一个用于通过Python和蓝牙低功耗协议控制Elgato Avea智能灯泡,并详细记录了其通信协议逆向工程过程的轻量级库。
Stars: 8 | Forks: 1
# 使用 Python 控制 Elgato Avea 灯泡
[](https://pypi.org/project/avea/)
[](https://github.com/k0rventen/avea/actions/workflows/python-publish.yml)
[Elgato 的 Avea 灯泡](https://www.amazon.co.uk/Elgato-Avea-Dynamic-Light-Android-Smartphone/dp/B00O4EZ11Q)是一款通过蓝牙连接到 iPhone 或 Android 应用程序的灯泡。
本项目旨在使用兼容蓝牙 4.0 的设备和一些 Python 魔法来控制它。
已在 Raspberry Pi 3 和 Zero W(带有内置蓝牙)上测试通过。
- [使用 Python 控制 Elgato Avea 灯泡](#control-of-an-elgato-avea-bulb-using-python)
- [太长不看](#tldr)
- [库的用法](#library-usage)
- [代码文档](#code-documentation)
- [灯泡的逆向工程](#reverse-engineering-of-the-bulb)
- [通信协议](#communication-protocol)
- [简介](#intro)
- [命令与负载说明](#commands-and-payload-explanation)
- [颜色命令](#color-command)
- [亮度命令](#brightness-command)
- [演练与示例](#walkthrough--example)
- [亮度](#brightness)
- [颜色](#color)
- [Python 实现](#python-implementation)
- [用于颜色计算的单行代码](#one-liner-for-color-computation)
- [Bleak write\_gatt\_char 用法](#bleak-write_gatt_char-usage)
- [使用 Bleak 处理通知](#working-with-notifications-using-bleak)
- [待办事项](#todo)
## 太长不看
该库需要 [bleak](https://github.com/hbldh/bleak),因此无论我们是使用 pip 还是从源码安装,都必须安装以下依赖。
**依赖项**
确保系统蓝牙协议栈可用(例如 Linux 上的 `bluez` 或 macOS 上内置的 CoreBluetooth 框架)。使用 pip 安装时,Python 依赖 `bleak` 会自动安装。
**然后通过 pip3 安装**
```
sudo apt install python3-pip
sudo pip3 install --upgrade avea
```
**或者,如果你更喜欢从源码安装**
```
git clone https://github.com/k0rventen/avea
cd avea
sudo python3 setup.py install
```
## 库的用法
你可以查看示例脚本 `example.py`,直接在你的灯泡上运行尝试:
```
sudo python3 example.py
```
下面是该库各种方法的快速入门指南。
**注意:根据你的操作系统配置,蓝牙发现可能需要额外的权限(例如在 Linux 上使用 sudo 运行,或者在 macOS 上授予蓝牙访问权限)。**
```
import avea # Important !
# 在列表中获取附近的 bulbs,然后检索所有 bulbs 的名称
# 根据平台的不同,discovery 可能需要提升的权限
nearbyBulbs = avea.discover_avea_bulbs()
for bulb in nearbyBulbs:
bulb.get_name()
print(bulb.name)
# 或者如果您知道其地址(例如在 scan 之后),则可以创建一个 bulb
myBulb = avea.Bulb("xx:xx:xx:xx:xx:xx")
# 您可以设置 brightness、color 和 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
# 并获取 brightness、color 和 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
```
就是这样。非常简单。
查看下面的解释以获取更多信息,或者直接查看源码!
## 代码文档
## 灯泡的逆向工程
我使用了 [Marmelatze](https://github.com/Marmelatze/avea_bulb) 提供的信息,以及通过 Android 设备上的 `btsnoop_hci.log` 文件和 Wireshark 进行的一些逆向工程。
下面是对 BLE 通信和用于与灯泡通信的 Python 实现的相当详尽的解释。
由于 BLE 通信相当复杂,如果你只是想使用该库,可以跳过所有这些内容。但这非常有趣。
## 通信协议
### 简介
灯泡使用蓝牙 4.0 "BLE" 进行通信,它为通信提供了一些有趣的特性,想了解更多关于它的信息请点击[这里](https://learn.adafruit.com/introduction-to-bluetooth-low-energy/gatt)。
总而言之,灯泡会发出一组具有 `characteristics` 的 `services`。我们使用后者与设备进行通信。
灯泡使用服务 `f815e810456c6761746f4d756e696368` 和相关的特征 `f815e811456c6761746f4d756e696368` 来发送和接收关于其状态(颜色、名称和亮度)的信息。我们将通过该特征进行传输。
### 命令与负载说明
传输的第一个字节是命令。可用的命令有:
Value | Command
--- | ---
0x35 | 设置 / 获取灯泡颜色
0x57 | 设置 / 获取灯泡亮度
0x58 | 设置 / 获取灯泡名称
### 颜色命令
对于颜色命令,传输负载如下:
Command | Fading time | Useless byte | White value | Red value | Green value | Blue value
---|---|---|---|---|---|---
负载中的每个值都是一个 4 位十六进制值。(实际值为 0 到 4095 之间的整数)
对于每种颜色,需要在十六进制值中添加前缀:
Color | prefix
---|---
White| 0x8000
Red | 0x3000
Green | 0x2000
Blue | 0X1000
然后,这些值将被格式化为**大端序**格式:
Int | 4-bytes Hexadecimal | Big-endian hex
---|---|---
4095 | 0x0fff| **0xff0f**
### 亮度命令
亮度同样是一个 0 到 4095 之间的整数值,以大端序的 4 字节十六进制值形式发送。传输内容如下所示:
Command | Brightness value |
---|---
0x57 | 0xff00
## 演练与示例
假设我们希望灯泡在 75% 亮度下显示粉色:
### 亮度
75% 的亮度大约是 3072(满值为 4095):
Int | 4-bytes Hexadecimal | **Big-endian hex**
---|---|---
3072 |0x0C00| **0x000C**
亮度命令将是 `0x57000C`
#### 颜色
粉色是 100% 红色、100% 蓝色,没有绿色。(我们假设白色值也为 0。)对于每种颜色,我们将整数值转换为十六进制,然后应用前缀,最后转换为大端序:
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
粉色灯泡的最终字节序列将是:
Command | Fading time | Useless byte | White value | Red value | Green value | Blue value
---|---|---|---|---|---|---
`0x35`|`1101`| `0000`| `0080`|`ff3f`|`0020`|`ff1f`
## Python 实现
下面是一些关于各个非常有趣的方面的 python3 代码。
### 用于颜色计算的单行代码
为了计算每种颜色的正确值,我创建了以下转换方法(此处以白色为例):
```
white = (int() | int(0x8000)).to_bytes(2, byteorder='little').hex()
```
### Bleak write_gatt_char 用法
Bleak 允许我们将原始二进制负载直接发送到特征而无需任何额外重写。该库现在将负载准备为字节并直接调用客户端:
```
await client.write_gatt_char(CONTROL_CHARACTERISTIC_UUID, payload, response=False)
```
### 使用 Bleak 处理通知
通知通过 `BleakClient.start_notify` 启用。在连接阶段,库会订阅 Avea 控制特征,并将每个通知路由到更新缓存灯泡状态的回调函数中。同步 getter 会等待一个 `asyncio.Event`,当接收到预期命令时会设置该事件,从而在底层利用 bleak 的同时保持公共 API 处于阻塞状态。
## 待办事项
- 对 `ambiances`(基于情绪的场景)进行逆向工程。
标签:BLE, bleak, Bluetooth 4.0, bluez, CoreBluetooth, Elgato Avea, IoT, Python, Python3, Raspberry Pi, 云资产清单, 内存执行, 开源库, 搜索引擎爬虫, 无后门, 智能家居, 智能家居自动化, 智能灯泡控制, 物联网, 硬件 hacking, 硬件接口, 蓝牙低功耗, 蓝牙控制, 边缘计算, 逆向工具, 逆向工程, 通信协议