☰
AnyPixel 后端架构全解:AppServer、ChromeBridge、UdpManager 与 Emulator 四组件协作实战
2026/10/15 3:51:25 网站建设 项目流程
  • 前端
  • 后端
  • IoT/嵌入式
  • 硬件开发

【免费下载链接】anypixel

A web-friendly way for anyone to build unusual displays

项目地址:https://gitcode.com/gh_mirrors/an/anypixel
点击查看免费下载

本指南以 AnyPixel 仓库 backend/README.md 为主线,系统讲解后端四大组件的职责分工、安装部署、核心源码与底层通信协议。读完本文,你将能够独立搭建一条从 JavaScript 前端应用、到浏览器桥接层、再到硬件或仿真显示器的完整数据链路,理解像素帧、按钮输入、状态上报三类 UDP 数据包在系统中的流转方式,并学会在无硬件条件下使用 Emulator 完成调试。

后端全景:四个组件如何协作驱动异形显示屏

AnyPixel 的设计目标是通过 Web 技术驱动“非常规”的显示设备(如由数千颗按钮 LED 构成的按钮墙)。后端作为前端 JavaScript 应用与物理硬件之间的桥梁,由四个独立应用协同工作:

  • AppServer:一个 Node.js 服务器,在本地托管 AnyPixel 应用(前端工程见 frontend 目录);
  • ChromeBridge:一个 Chrome 应用,负责展示 AnyPixel 应用,并处理应用与物理显示硬件之间的通信;
  • UdpManager:一个 Node.js 应用,将 ChromeBridge 发来的捆绑数据包拆分并分发到各个硬件组件;
  • Emulator:一个用于调试的 Chrome 应用,在软件层面模拟 ChromeBridge 与物理显示硬件之间的网络通信。

四个组件的数据流方向为:AppServer(应用画布像素)→ ChromeBridge(拆分/捆绑打包)→ UdpManager(解包分发)→ 物理显示单元或 Emulator;反向则携带按钮输入状态与硬件状态数据从控制板/Emulator 回流至 ChromeBridge,再由 ChromeBridge 转发给当前应用。整体架构如下:

环境准备

搭建后端环境前,需要先安装两类基础工具(backend/README.md 的 Installation & Startup 一节):

  1. 安装 node.js:所有 Node.js 应用(AppServer、UdpManager)的运行环境;
  2. 全局安装 browserify:$ npm install -g browserify,用于把 CommonJS 风格的源码打包成浏览器可直接执行的单文件。

以下安装命令均需在对应的组件目录内执行。若使用npm start启动进程,停止时使用npm stop(两处 README 均以此约定)。

AppServer:应用宿主与 REST API

AppServer 是一个运行在 Express 上的 Node.js 应用(入口见 backend/appserver/appserver.js),它持有 AnyPixel 应用绘制所用的画布:每一帧将画布像素数据发送给 ChromeBridge,再由 ChromeBridge 转交物理显示器;同时对外提供用于“切换当前应用”和“列出可用应用”的 RESTful API(backend/appserver/README.md)。

安装与启动

# 在 /backend/appserver 目录下 $ npm install # 安装依赖,postinstall 会自动执行 browserify 构建 $ npm start # 启动服务(默认监听 8000 端口) $ npm stop # 停止服务

package.json(backend/appserver/package.json)中的脚本约定如下:

脚本实际执行说明
preinstallnpm install -g forever自动安装进程守护工具 forever
buildbrowserify -d src/app-runner.js > public/js/index.js将应用运行器打包为public/js/index.js
startforever start --minUptime 1000 --spinSleepTime 1000 appserver.js以守护进程方式启动
stopforever stop appserver.js停止进程

服务启动后监听 8000 端口(源码 appserver.js 中http.createServer(app).listen(8000)),并在控制台输出AnyPixel AppServer | Listening on port 8000。

添加自己的应用

把应用文件夹复制到public/apps目录下,并确保应用根目录下存在index.js文件即可被托管(backend/appserver/README.md)。仓库自带的示例应用 getting-started-rpi 展示了标准结构:

{ "name": "getting-started-rpi", "display_name": "Getting Started RPi", "version": "1.0.0", "main": "index.js", "scripts": { "build": "browserify -d src/app.js > index.js", "postinstall": "npm run build" }, "dependencies": { "anypixel": "^1.0.0" } }
  • index.js是应用的入口,由browserify从src/app.js打包生成;
  • package.json中的display_name字段会被/api/apps接口读取,作为应用在列表中的显示名(下文源码解析可见);
  • 应用内部通过 frontend/framework/lib/anypixel.js 提供的框架与后端交互,例如 getting-started-rpi 的 app.js 中通过anypixel.canvas.getContext2D()获取画布、监听onButtonDown/onButtonUp事件响应按钮输入。

REST API 详解

AppServer 暴露两个接口(backend/appserver/README.md 的 API 一节):

1. 获取应用页面

  • URL:/app/:app_name
  • 方法:GET
  • URL 参数:app_name=[string]
  • 成功响应:Code200,返回渲染后的 EJS 模板 HTML,页面中包含该应用;
  • 失败响应:Code404,返回文本Invalid app name。

2. 获取可用应用列表

  • URL:/api/apps
  • 方法:GET
  • URL 参数:无
  • 成功响应:Code200,返回 JSON:
{ "success": true, "apps": [ { "name": "My App", "path": "myApp" } ] }
  • 失败响应:无(源码中无错误分支)。
源码级解析

/app/:app_name的处理逻辑(appserver.js)会先拼接public/apps/<app_name>/index.js并检查文件是否存在,不存在则返回 404;存在则渲染 views/app.ejs 模板。该模板包含一个id="button-canvas"的<canvas>(宽 140、高 42,对应配置 frontend/framework/lib/config.js 中的width: 140, height: 42),并以<base href="/apps/<app_name>/">保证应用静态资源相对路径正确,同时依次加载打包好的/js/index.js与应用入口index.js。

/api/apps的实现(appserver.js)会扫描public/apps下所有非隐藏目录,读取每个目录中package.json的display_name作为应用名称,并按名称字母序排序后返回。

应用运行器 src/app-runner.js 则完成帧级采样:页面加载后监听message事件与 ChromeBridge 建立通信,当收到buttonWallReady事件后,以setInterval(..., 17)(约 60fps,源码注释1 / 60fps ≈ 17ms)周期性调用 src/anypixel-app.js 的updateFrame()——读取 Canvas 的 2D(getImageData)或 WebGL(readPixels,并翻转 Y 轴)像素,丢弃 Alpha 通道,在首字节写入头部0(像素数据标记)后通过postMessage发送给 ChromeBridge;同时解析 ChromeBridge 回传的按钮状态消息(首字节为 0 的消息体为{p:{x,y}, s:state}序列),派发buttonStates自定义事件。

ChromeBridge:应用与硬件的桥梁

ChromeBridge 是一个 Chrome 应用),既承担应用与物理硬件之间的通信,也提供监控、控制物理硬件状态的用户界面。

安装与启动

  1. 在/backend/chromebridge目录执行$ npm install(postinstall会自动执行npm run build,将 src/view-controller.js 打包为根目录index.js,见 package.json);
  2. 打开 Chrome 浏览器,访问chrome://extensions;
  3. 开启开发者模式(Developer mode):
  4. 点击"Load unpacked extension..."按钮,选择/backend/chromebridge文件夹,ChromeBridge 应用会出现在扩展列表顶部;
  5. 确保AppServer 与 Emulator 正在运行(或已接入物理显示硬件);
  6. 点击扩展标题下方的"Launch"按钮启动应用:

如需手动重新构建,可执行$ npm run build。

硬件接口:像素数据与按钮输入

ChromeBridge 与硬件交互包含两个方向(backend/chromebridge/README.md 的 Hardware Interface 一节):

像素数据(下行):每一帧,ChromeBridge 从 AppServer 当前托管的应用处接收整帧原始像素数据,根据显示单元(display unit)负责渲染的画布区域对其进行切分;每个显示单元的像素数据被封装为数据包,并携带与之对应的控制器板的 IP 地址与端口。为提升吞吐量,多个显示单元的包会被捆绑进单个 UDP 包,发送给udp-manager,由其解包并依据包内 IP 分发。

按钮输入(上行):控制板每 33ms 上报一次按钮状态列表,ChromeBridge 对比上一帧找出状态发生变化的输入,并把对应输入事件分发给当前应用。

源码级解析
  • 应用管理类 src/app-controller.js:通过fetch(AppController.baseAppUrl + '/api/apps')(baseAppUrl为http://localhost:8000)拉取应用列表填充下拉框;应用加载完成后发送handshake消息与 webview 内应用握手;所有postMessage消息中首字节为0的均视为像素数据,转交DisplayController.updateFrame()。
  • 显示控制类 src/display-controller.js:initWithConfig(config)根据配置类(config.physical或config.emulator)实例化显示单元与电源单元;updateFrame()中调用DisplayConfig.pixelToUnit()将每个像素映射到所属显示单元,再由savePixelPacket()生成各单元的tx_8BitColor数据包,最后按DisplayConfig.displayUnitsPerPacket将多个单元捆绑为单个 UDP 包,通过chrome.sockets.udp.send发往UdpConfig.udpManagerIP:udpManagerPort。
  • 显示单元类 src/display-unit.js:维护该单元 RGB 数组、输入状态与健康状态;updateStatus()中依据 32 位 uptime 的增量判断各板是否存活(时间差小于UPTIME_ALERT_TIMEOUT = 10000毫秒视为健康),并支持向单元下发 dot correction 与 calibration 数据。
  • 电源单元类 src/power-unit.js:解析电源状态包,依据 6 路 24V 输出、2 路 AC 输入、2 路继电器状态计算isHealthy,并可构造tx_relayControl命令控制两列继电器的通断。

用户界面功能

backend/chromebridge/README.md 的 User Interface 一节列出了四大监控/控制能力:

  • 应用状态监控(App Status Monitoring):通过 Application 下拉框切换当前应用;下拉列表在 ChromeBridge 首次启动时填充,新增应用后需点击Reload app list刷新列表。
  • 像素流(Pixel Streaming):使用Pause按钮暂停/恢复向显示硬件发送像素数据;传输校准或 dot correction 数据时,像素流会被自动暂停(对应源码 calibration/dot-correction-data.js 中发送前调用ViewController.pausePixelStreaming()、发送后resumePixelStreaming())。
  • 硬件状态监控(Hardware Status Monitoring):硬件约每秒上报一次系统健康状态,包括每颗 LED 的温度、各板 MAC 地址、运行时长以及电源是否正常(状态包解析见 backend/config/packets/display-unit-status-packet.js:每像素 1 字节温度、每板 1 字节风扇转速、每板 6 字节 ID、每板 4 字节 uptime、每板 1 字节平均温度)。
  • 显示单元监控/控制(Display Unit Monitoring / Control):整体健康度以网格呈现,异常的单元显示为红色、稳定单元为绿色;点击单元可查看逐像素温度、板 MAC 地址与运行时长,并可单独设置该单元的 dot correction 与 calibration 数据。
  • 电源单元监控/控制(Power Unit Monitoring / Control):每个电源单元为其上方 6 个显示单元供电,同样以绿/红表示健康状态;点击可查看电源各部件状态,并支持切换电源继电器的开/关。

UdpManager:捆绑包的拆分与分发

UdpManager 是一个 Node.js 应用,接收来自 ChromeBridge 的像素数据并将其分发给每个显示单元(backend/udp-manager/README.md)。

安装与启动

# 在 /backend/udp-manager 目录下 $ npm install # 安装依赖(preinstall 自动安装 forever) $ npm start # 启动管理器 $ npm stop # 停止管理器
源码级解析

核心逻辑位于 backend/udp-manager/udp-manager.js,要点如下:

  • 监听地址由 backend/config/config.udp.js 决定:server.bind(udpConfig.udpManagerPort, udpConfig.packetBundleIP),即默认在127.0.0.1:27483上监听捆绑包;
  • 收到消息后根据首字节区分数据类型(udp-manager.js):
    • 1:8-bit 数据,每单元包长为pixelsPerUnit * 3 + 16(16 为发送头长度PacketBuilder.txHeaderLength,见 backend/config/packets/packet-builder.js);
    • 2:12-bit 数据,每单元包长为pixelsPerUnit * 3 * 1.5 + 16(12-bit 数据每 3 个像素占 4.5 字节,即 1.5 倍 8-bit 字节);
  • splitAndSend()按上述包长把捆绑包切成独立单元包,并从每个包头部字节 10~13读出目标 IP、从字节 14~15以(byte << 8) + byte还原端口(udp-manager.js),最后通过client.send()发往对应控制板。

由此可以直观看出捆绑通信的报文结构:ChromeBridge 在打包时即把每个目标单元的 IP 与端口写入各自包头(见PacketBuilder.buildTxPacket对ip_address与port字段的填充),UdpManager 只是忠实地拆包并按包头路由。

Emulator:无需硬件的调试利器

Emulator 是一个 Chrome 应用)。

安装与启动

  1. 在/backend/emulator目录执行$ npm install;
  2. 安装与加载方式与 ChromeBridge 完全一致,唯一区别是加载扩展时选择/backend/emulator文件夹(backend/README.md 的 Emulator 一节);
  3. 同样通过扩展列表的"Launch"按钮启动。

如需手动重新构建,可执行$ npm run build(见 package.json,构建脚本为browserify -d src/emulator.js > index.js)。

配置仿真地址

仿真器的 IP/端口配置位于 backend/config/config.emulator.js。由于仿真完全在本地运行,所有单元共用127.0.0.1,以端口区分:

  • 显示单元:按 3 行 × 10 列排列,端口依次为3001~3010、3011~3020、3021~3030;
  • 电源单元:5 个电源单元,端口为3031~3035。

修改配置文件的 IP/端口后,Emulator 与 ChromeBridge 都需要重新构建(npm run build),因为 ChromeBridge 侧通过DisplayController.initWithConfig(config)使用同一份地址表实例化单元对象。

源码级解析

backend/emulator/src/Emulator.js 在页面加载后依据EmulatorConfig.unitAddresses创建 30 个模拟显示单元与 5 个模拟电源单元,每个单元绑定0.0.0.0的对应端口并注册chrome.sockets.udp.onReceive;收到消息后,通过data_8v[0]判断命令类型(tx_relayControl路由给电源单元,其余路由给显示单元),并依据data_8v[8](头部unit_number字段)定位目标单元。渲染循环使用requestAnimationFrame把各单元像素绘制到画布。

模拟显示单元 backend/emulator/src/emulator-display-unit.js 还原了硬件行为:接收像素包(校验首字节必须为1)后,若所在电源单元的对应继电器开启则渲染真实 RGB 并按亮度推算模拟温度(70 + avgNormalized * 50,单位华氏度),否则渲染黑色模拟断电;每 33ms 上报一次变化的按钮状态(按位打包,每按钮 1 bit),每 1 秒上报一次模拟状态包(随机生成各板 MAC 地址、填充模拟风扇转速与平均温度)。模拟电源单元 backend/emulator/src/emulator-power-unit.js 则每秒上报电源状态,并响应tx_relayControl包更新继电器状态。

核心配置与通信协议

显示与电源拓扑配置

backend/config/config.display.js 定义了硬件显示器的像素级拓扑,注释明确指出:像素 → 显示板(board)→ 显示单元(unit)三层结构,本仓库的默认配置为:

配置项值含义
boardRows/boardCols7 / 7每块显示板 7×7 像素
boardsPerUnitCols/boardsPerUnitRows2 / 2每个显示单元含 2×2 块显示板(即每单元 14×14 像素)
unitCols/unitRows10 / 3整屏 3 行 × 10 列显示单元
cols/rows(推导)140 / 42整屏 140×42 像素
pixelsPerUnit(推导)196每单元像素数
numUnits(推导)30全屏单元总数
displayUnitsPerPacket(推导)6每个捆绑 UDP 包承载的单元数

该文件还提供了pixelToUnit、unitPixelToRowCol、unitNumberToGlobalUnitRowCol、rowColToUnitNumber等映射函数,ChromeBridge 与 UdpManager 正是依赖这些函数完成像素归属判定与坐标换算。类似的,backend/config/config.power.js 描述电源系统:每个电源单元负责 3×2 区域的显示单元,内含 2 路电源,每路 3 路 DC 输出、1 路 AC 输入、1 路控制继电器。

UDP 端口与硬件地址

backend/config/config.udp.js 定义了系统内的端口分工:

配置项默认值用途
controllerPort27482与控制板通信的端口
udpManagerPort27483与 UdpManager 通信的端口
udpManagerIP127.0.0.1UdpManager 的 IP
emulatorIP127.0.0.1Emulator 的 IP

物理硬件模式下的地址表见 backend/config/config.physical.js:30 个显示单元使用192.168.0.20~192.168.0.49(按 3 行 × 10 列排列,端口统一为7),5 个电源单元使用192.168.0.50~192.168.0.54。ChromeBridge 通过 src/display-controller.js 的initWithConfig选择使用物理或仿真地址表。

数据包格式与命令标志

所有与硬件交互的数据包由 backend/config/packets/packet-builder.js 统一构造:

  • 发送头(txHeader,16 字节):字节 0 为command_flag,字节 8 为unit_number,字节 9 为sequence_number,字节 10~13 为目标 IP,字节 14~15 为目标端口;
  • 接收头(rxHeader,4 字节):字节 0 为command_flag,字节 1 为unit_number;
  • 命令标志(commandFlags):tx_8BitColor = 0x01(8-bit 颜色)、tx_12BitCalLookup = 0x05(12-bit 校准查找表)、tx_8BitDotCorrection = 0x06(8-bit dot correction)、tx_relayControl = 0x10(继电器控制)、req_reboot = 0xF0(请求重启)、rx_inputState = 0x20(输入状态)、rx_statusData = 0x21(显示状态)、rx_powerData = 0x22(电源状态)、rx_12BitCalLookup = 0x23(12-bit 校准表数据)。

固件侧的完整协议说明见 firmware/PROTOCOLS.md,其中头部字段(Command ID、Unit Address、IP Address、Port 等)与上表一一对应,并详细列出 0x01(Set RGB 8-bit,RGB888 逐像素填充)、0x10(Relay enable,两字节分别控制 Relay 1/2)、0xF0(Reboot board,首 4 字节为校验和防误触、末字节含 CTRL 与四象限 LED 板标志)等命令的载荷格式,可作为调试与二次开发时的权威参考。

校准与 dot correction 数据

为保证整屏亮度均匀,ChromeBridge 内置了两种校正数据:

  • dot correction(backend/chromebridge/src/calibration/dot-correction-data.js):针对 TLC59401 恒流 PWM 驱动芯片的参考电流调节,默认值为{r: 50, g: 100, b: 50},合法范围 0~100,芯片上限MAX_DC_VALUE = 63,发送时按round(63 * value / 100)缩放为每像素 3 字节;
  • calibration(backend/chromebridge/src/calibration/calibration-data.js):由 gamma 为 2.2 的校正曲线生成 65 组查找表(因 STM32 微控制器内存限制采用 64 值 + 线性插值),每组表对每像素写入 3 通道值;12-bit 数据在传输前需经repack12bitTo8bit压缩进 8-bit 字节流,发送时每个表索引(tableEntry)以 33ms 间隔顺序下发。

端到端运行流程与调试建议

将上述组件串联起来,完整的运行流程如下:

  1. 启动AppServer(npm start),在public/apps下放置至少一个应用;
  2. 启动UdpManager(npm start),在127.0.0.1:27483上等待捆绑包;
  3. 在 Chrome 中加载并启动ChromeBridge扩展;其首次启动即通过/api/apps拉取应用列表,并自动(或经下拉框选择)加载当前应用;
  4. 根据是否具备物理硬件二选一:接入物理硬件(配置config.physical.js的地址表)或启动Emulator(加载/backend/emulator扩展,配置config.emulator.js);
  5. 应用画布每帧(约 60fps)经postMessage将像素交给 ChromeBridge → ChromeBridge 按单元切分、捆绑成 UDP 包 → UdpManager 解包后按包头 IP/端口分发 → 硬件/仿真器点亮对应像素;同时按钮状态(33ms)与硬件状态(约 1s)反向回流,驱动应用交互与监控面板刷新。

对于开发调试场景,推荐按“AppServer + UdpManager + Emulator”组合运行,无需硬件即可验证应用的像素输出与按钮交互逻辑(仓库 rpi-example 目录提供了树莓派场景下 ChromeBridge 与 Emulator 的实际运行截图参考);当发现 Emulator 显示位置异常或通信不达预期时,优先核对config.emulator.js的端口表与config.udp.js的端口是否一致,并注意修改配置后需同时重建 Emulator 与 ChromeBridge。

  • 前端
  • 后端
  • IoT/嵌入式
  • 硬件开发

【免费下载链接】anypixel

A web-friendly way for anyone to build unusual displays

项目地址:https://gitcode.com/gh_mirrors/an/anypixel
点击查看免费下载
上一篇:爪爪 PawWork live-page action 完全实操手册:3 个核心 op 驱动真实网页
下一篇:一条用户消息的完整旅程:爪爪 PawWork sendMessage 工具循环源码解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询