mrfyda/trmnl-joan-bridge
GitHub: mrfyda/trmnl-joan-bridge
一个独立的 Go 服务器,通过逆向 Visionect PV3 协议让 Joan 6 墨水屏脱离云端、直接由自建 TRMNL 服务器驱动显示。
Stars: 0 | Forks: 0
# trmnl-joan-bridge
一个独立的 Go server,可以从
[TRMNL](https://github.com/usetrmnl/byos_hanami) (BYOS) server 驱动一台 **Joan 6** 墨水屏 —— 并且
**不依赖 Visionect 云端 (VSS)**。
Joan 6 是一块 13 英寸、分辨率为 1024×758 的 4-bit 灰度墨水屏,配备电容
触摸屏。开箱即用时,它只能与 Visionect 的托管软件进行通信。这个
shim 重新实现了设备端的通信协议,从而让这块屏幕可以指向
你自行控制的服务器,并显示任何你可以渲染成图片的内容 —— 无论是 Home
Assistant 仪表盘、TRMNL 插件、还是一个时钟等等。
```
┌─────────┐ PV3 / TCP:11112 ┌───────────────────┐ HTTP /api/display ┌──────────┐
│ Joan 6 │ ◀─────────────────▶ │ trmnl-joan-bridge │ ◀───────────────────▶ │ TRMNL │
│ e-ink │ image frames │ (this repo) │ image_url + rate │ (BYOS) │
└─────────┘ └───────────────────┘ └──────────┘
```
## 工作原理
1. **轮询 TRMNL**:使用标准的 TRMNL 设备协议,即 `GET /api/display`
并带有 `ID`(设备 MAC)和 `Access-Token` 请求头。TRMNL 会返回
一个 `image_url` 和一个 `refresh_rate`。
2. **获取并编码**:将图像编码为 Visionect **PV3** 帧:调整大小至
1024×758,转换为 4-bit 灰度,按 2 像素/字节打包,分割为 80 个
LZ4 压缩块,并附带设备描述符和请求头。
3. **服务屏幕**:通过端口 11112 上的原始 TCP 进行服务。Joan 会建立一个连接,大约每 3 分钟
发送一次状态 "hello";shim 会回复一个
会话 ACK,并在图像有更新时回复该帧。当屏幕只有一部分
发生变化时,它会发送一次 **partial update** —— 仅发送变更的矩形区域,因此
屏幕会进行快速、无闪烁的局部刷新,而不是全屏重绘;当
全屏变化(或(重新)连接后的首次推送)时则会发送完整帧。
参见 [`docs/partial-updates.md`](docs/partial-updates.md)。
4. **上报设备健康状态。** 状态 hello 中还包含电池电压和
WiFi RSSI;shim 会对其进行解析,并作为标准的
`Battery-Voltage` 和 `RSSI` 请求头转发给 TRMNL,因此电量和信号强度会显示在
TRMNL 设备仪表盘中。参见 [`docs/status-hello.md`](docs/status-hello.md)。
PV3 的通信格式、块布局和会话握手都是通过逆向工程
捕获的设备流量得出的;协议相关说明详见 `docs/`。
## 快速开始
该镜像在每次推送到 `main` 分支时,会由 CI 以多架构(arm64 + amd64)形式发布到 GitHub Container
Registry。
### Docker
```
docker run -d --name trmnl-joan-bridge \
-p 11112:11112 \
-e TRMNL_SERVER="http://your-trmnl-host:2300" \
-e DEVICE_ID="AA:BB:CC:DD:EE:FF" \
-e ACCESS_TOKEN="your-trmnl-device-token" \
ghcr.io/mrfyda/trmnl-joan-bridge:latest
```
### Portainer / Docker Compose
```
services:
trmnl-joan-bridge:
image: ghcr.io/mrfyda/trmnl-joan-bridge:latest
restart: unless-stopped
ports:
- "11112:11112"
environment:
TRMNL_SERVER: http://your-trmnl-host:2300
DEVICE_ID: AA:BB:CC:DD:EE:FF # Joan MAC, UPPERCASE
ACCESS_TOKEN: your-trmnl-device-token
```
然后使用 **Joan Configurator** 应用程序将屏幕指向该 shim:把
服务器地址设置为 `your-shim-host:11112`。
## 配置
所有配置均通过环境变量(或等效的 flags)进行。
| 变量 | 必需 | 默认值 | 描述 |
| ------------------ | -------- | --------- | ------------------------------------------------------------------ |
| `TRMNL_SERVER` | 是 | — | TRMNL 基础 URL,例如 `http://192.168.1.10:2300` |
| `DEVICE_ID` | 是 | — | Joan MAC 地址,**大写**,例如 `AA:BB:CC:DD:EE:FF` |
| `ACCESS_TOKEN` | 是 | — | TRMNL 设备访问 token |
| `REFRESH_INTERVAL` | 否 | `60s` | 当 TRMNL 省略 `refresh_rate` 时的后备重新获取间隔 |
| `LISTEN_ADDR` | 否 | `:11112` | 屏幕连接的 TCP 地址 |
## 从源码构建
```
# 本地 binary(需要 Go 1.22+)
go build -o bin/trmnl-joan-bridge .
# 或者通过 Makefile,使用 container toolchain
make build # native dev binary → bin/trmnl-joan-bridge-local
make build-arm # static linux/arm64 binary → bin/trmnl-joan-bridge
# Docker image
docker build -t trmnl-joan-bridge .
```
## 硬件说明
- **屏幕:** Joan 6 —— 1024×758,4-bit 灰度墨水屏,支持电容触摸。
- **传输层:** 基于 TCP 端口 11112 的 Visionect PV3。
- **旋转:** 编码器会应用固定的 180° 旋转,以匹配该屏幕的
扫描方向。
## 仓库结构
```
main.go TCP server, TRMNL polling, frame store, heartbeat loop
pv3/ PV3 wire protocol: framing + decode, full & partial frame encode, session ACK
docs/ reverse-engineered protocol notes
Dockerfile multi-stage build → scratch runtime image
.github/ CI: gofmt/vet/test + multi-arch build & push to ghcr.io
```
## 致谢
纯粹为了实现互操作性而对 Visionect 设备协议进行逆向工程构建,以避免将一块完好的显示屏沦为垃圾填埋场的废弃物。
标签:EVTX分析, Go, Ruby工具, TRMNL, 协议桥接, 日志审计, 智慧家居, 服务器, 物联网, 电子墨水屏, 请求拦截