deonspengler/akp02
GitHub: deonspengler/akp02
Ajazz AKP02 USB 副屏的 Linux 驱动库,基于逆向工程还原 USB HID 协议,实现全屏刷新、局部更新、亮度与方向控制等功能。
Stars: 0 | Forks: 0
# akp02
**Ajazz AKP02** 9.2" (1920x462) USB 副屏的 Linux 驱动库。
AKP02 官方仅随附 Windows 专用软件;本项目是对其 USB HID 协议
进行逆向工程的结果,以便该屏幕可以在 Linux 下被原生驱动。
已在真实硬件上确认:全屏帧刷新、局部(区域)更新(包含正确的颜色渲染)、
亮度调节、屏幕开关、清屏、固件版本查询、开机/显示方向设置,
以及持久的 keepalive 运行状态。
## 安装
```
pip install .
```
然后允许非 root 用户访问该设备:
```
sudo cp udev/99-akp02.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger
```
(安装规则后请重新插入设备)
## 库
```
from akp02 import AKP02
with AKP02() as panel:
panel.start_keepalive() # device sleeps without heartbeats
panel.set_brightness(80)
panel.set_boot_orientation("vertical") # live + persists across power cycles
panel.show(pil_image) # full screen, letterboxed if needed
panel.show(widget, at=(1600, 16)) # partial update, rest preserved
```
所有公共方法均是线程安全的:在完整的多 report 图像传输期间会持有同一个
锁,因此 keepalive 线程绝不会交错执行或损坏帧。图像合成和 JPEG 编码
发生在锁之外,因此 keepalive 线程不会被它们阻塞。
在全屏绘制之后紧接着发送的区域更新需要先有一个短暂的
稳定延迟,否则全屏帧可能会完全不渲染——
这已被跟踪并自动应用;调用方无需
进行任何操作。
区域定位也会被自动纠正:除非区域的位置满足特定的、
已确认的对齐规则(参见下文的协议说明),否则设备会以
错误的颜色渲染该区域。`show()` 在需要时会将
位置微调几个像素,并在执行此操作时发出警告,因此
`at=(x, y)` 始终能正确渲染,而无需调用方
了解这一点。
## 测试
```
pip install -e ".[test]"
pytest
```
105 个测试,100% 的行和分支覆盖率,完全针对虚拟的
HID 设备运行——无需物理硬件或安装 `hidapi`。
涵盖与真实抓包数据严格匹配的协议字节精确度、
基于真实硬件确认数据点的区域颜色对齐纠正,
以及并发行为:断开连接后的死线程恢复、
针对死锁设备的有限关闭,以及在真实竞争条件下验证的
锁交错预防。
## 协议说明(逆向工程)
纯 USB HID,无加密。VID:PID 为 `0300:3017`。输出 report 为
1024 字节(EP1 OUT),输入 report 为 512 字节(EP2 IN)。
**命令** 是一个零填充的 report:
`"CRT" + 00 00 + <助记符> + 00 00 + <参数>`
| 助记符 | 动作 |
|-----------|---------------------------------------------------------|
| `HAN` | 屏幕关闭 |
| `DIS` | 屏幕开启 |
| `LIG` | 亮度(1 个参数字节,0-100) |
| `CONNECT` | 心跳(如果没有周期性心跳,设备会休眠) |
| `STP` | 提交 / 渲染已缓冲的图像数据 |
| `SET` | 开机/显示方向(见下文) |
布局例外:`CLE` (清屏) 使用 3 字节间隙加上字面量 `0xFF`
尾部,而不是通常的 2 字节间隙;`VER` (固件版本) 在
`CRT` 之前有一个前导的 `0x00` 设备上下文字节,助记符后
无间隙,并通过 IN endpoint 上的同步 `GET_REPORT`
返回其答案。间隙大小在各命令之间**并不**统一——
添加新命令时请逐一验证。
**图像传输**:一个 32 字节的 `CRT..DRA` header(大端序长度 =
payload + 0x20,接着是作为大端序 uint16 的宽/高/x/y,全屏绘制时全为零)
紧接着是 JPEG 字节,分块为
1024 字节的 report,然后是 `STP`。JPEG 是 462x1920 的**竖屏** ——
横向内容顺时针**旋转** 90 度(已在硬件上确认;反向旋转会使屏幕
上下颠倒)。
非零 header 坐标绘制局部区域(竖屏空间);
其外部的内容将被保留。
**区域颜色对齐**(在真实硬件上经验证确认):区域更新会以错误颜色渲染——
不是位置偏移——
除非实际落入 header 中的位置
(`portrait_x = 462 - y - height`,以横向术语而言)满足
`portrait_x % 8 == 2`。根本原因已探明,而非仅仅观察到的现象:462
(适用此规则的轴)不能像 1920(另一条轴,没有表现出
同等敏感性)那样被均匀地划分为 8 或 16 像素的
JPEG 块,因此设备的固件显然在该轴上以固定的
内部偏移量填充其缓冲区。库会自动纠正此问题,
而不是要求调用方选取特殊的
坐标。
**稳定延迟**:在全屏绘制之后立即发送的区域更新
可能完全无法渲染全屏帧,除非有
一个短暂的延迟(已确认:4ms 即足够,0ms 会失败;库使用
20ms 作为裕量)将它们隔开。区域接区域、以及
全屏接全屏均不需要延迟。可能的原因:全屏绘制是
一次干净的缓冲区替换,而区域绘制是对当前 framebuffer 的读-改-写;
如果该读取在之前的提交真正完成内部稳定之前就开始,传输中的
提交显然可能会被损坏或中止。
**开机/显示方向**(在真实硬件上确认):`"CRT" +
00,00 + "SET" + 00,00 + 0x00 + <方向字节>`,其中
方向字节为 `0x00`(水平)或 `0x01`(垂直)。
这是通过对比两次具有不同结果的相同动作的真实抓包发现的——
第一次抓包只显示了默认值
(与“无参数”难以区分),第二次具有实际
模式切换的抓包揭示了真实的字节。已确认这不仅会立即
应用于实时显示,*而且*会作为开机默认值
持久化:通过在设置每个值后对设备进行物理断电重启,并观察开机启动画面方向
匹配来验证。
hidapi 注意事项:每个 report 的 `write()` 需要一个前导的
`0x00` report-ID 占位符;内核会在链路上将其剥离(抓包显示
链路上没有 report-ID 字节)。
## 许可证
MPL-2.0
标签:USB HID, 副屏显示, 协议逆向, 硬件交互, 逆向工具, 驱动库