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, 协议桥接, 日志审计, 智慧家居, 服务器, 物联网, 电子墨水屏, 请求拦截