一个面向 Agent 的树莓派 GPIO 控制框架。Host Server 提供网页、REST/OpenAPI 和审计记录;树莓派 Client 主动连接 Server,执行经过配置白名单约束的 GPIO 命令,并持续上报节点、系统和 IO 状态。
Agent / Browser
│ REST (optional X-API-Key)
▼
FastAPI Host Server ─── SQLite audit log
▲
│ authenticated WebSocket
│ commands / acknowledgements / telemetry
▼
Raspberry Pi Client ─── gpiozero ─── relay / sensor
- Client 主动连接 Host,局域网里无需向树莓派开放控制 API。
- Server 支持免密钥局域网模式,也可要求
X-API-Key;每个节点的 WebSocket 始终使用后台配置的独立X-Node-Token,用户操作时无需输入节点 token。 raspberry-pi-40pin配置会列出 26 个通用 GPIO,物理 27/28 脚作为 HAT ID 保留,不开放控制。- GPIO 初始保持
unconfigured,网页或 API 明确配置后才会被程序申请,避免启动时抢占 I²C、SPI 和 UART 复用引脚。 - Client 启动、断线、退出、释放、重配置或命令脉冲结束时,输出回到各自的
safe_state;单路清理失败不会跳过其他已申请引脚的清理。 - Server 把连接与命令审计写入 SQLite,前端每 2 秒刷新节点状态。
- 节点显示名默认为
Pi,可在网页中点击名称重命名;名称由 Host SQLite 持久保存,不改变内部node_id、连接地址或 token。 - Client 优先从设备树读取树莓派板级序列号,回退到
/proc/cpuinfo的Serial,并在设备摘要中作为只读id显示。
项目支持从 Raspberry Pi 3B 到 Raspberry Pi 5 的标准 40-pin GPIO 排针,并对各代 SoC / GPIO 控制器显示对应的 Pinmux 复用功能:
| 硬件系列 | 已支持型号 | GPIO / Pinmux 控制器 | 复用功能范围 |
|---|---|---|---|
| Raspberry Pi 3 | 3B、3B+、3A+、Zero 2 W、CM3/CM3+ | BCM2837 / RP3A0(BCM283x GPIO) | ALT0–ALT5 |
| Raspberry Pi 4 | 4B、400、CM4/CM4S | BCM2711 | ALT0–ALT5 |
| Raspberry Pi 5 | Pi 5、CM5 | RP1 | ALT0–ALT8 |
- 所有型号统一使用 BCM GPIO 编号。
raspberry-pi-40pinprofile 提供 GPIO2–GPIO27 共 26 个通用 GPIO;物理 27/28 脚是 HAT ID 保留脚,不开放为通用 GPIO。 - 通用 GPIO 默认保持
unconfigured,只有收到明确的配置命令后才会申请,避免启动时占用 I²C、SPI 或 UART 引脚。 - Client 组合
/proc/device-tree/model与compatible自动识别硬件,并优先通过只读的pinctrl -c ... funcs获取当前系统的功能表;命令不可用时回退到项目内置数据。无法识别的硬件不会误套其他型号的 Pinmux 表。 - Compute Module 仅在载板确实提供兼容的 40-pin 排针时适用,且需要显式配置
board_profile = "raspberry-pi-40pin"。 - Pi 5 需要使用当前 Raspberry Pi OS;系统应将 RP1 用户排针暴露为
gpiochip0,由gpiozero/lgpio使用 BCM 编号访问。
内置 Pinmux 数据参考 Raspberry Pi 官方的 BCM2711 外设手册 与 RP1 外设手册。
需要 Python 3.11+:
python3 -m venv .venv
.venv/bin/pip install -e '.[server,dev]'
cp .env.example .env编辑 .env。仅在可信局域网使用时可设 HOA_AUTH_MODE=none,网页和 API 无需密钥;设为 api_key 时务必更换示例密钥:
chmod +x scripts/*.zsh
./scripts/run-server.zsh打开 http://HOST_IP:8000/ 查看面板,OpenAPI 位于 http://HOST_IP:8000/docs。
节点头部以两行设备摘要显示可重命名名称、由 Host 连接端确认的 IP、板级序列号、型号及遥测信息;内部节点 ID 不在页面显示。面板默认使用英文,右上角的 ZH/EN 按钮可切换中英文,选择会保存在当前浏览器中。右上角的 PINMUX 按钮默认关闭;点击后会在全部 GPIO 旁显示与当前硬件匹配的复用功能,再次点击即隐藏。该视图仅供接线和 Device Tree 配置参考,不会改变引脚模式。排针图中的每个 GPIO 都带有独立的 Direction、Level 下拉和一键“释放”按钮;输入上下拉等完整参数通过引脚框内的“高级”按钮设置。
如需随桌面用户会话自动启动,可链接仓库内的 user service:
systemctl --user link "$PWD/deployment/hand-of-agents-server-user.service"
systemctl --user enable --now hand-of-agents-server-user.service如果希望注销后仍保持服务运行,需要管理员执行 loginctl enable-linger USERNAME;也可以改用 deployment/hand-of-agents-server.service 安装为系统服务。
复制 configs/pi-lab.toml.example 并按实际接线填写。board_profile = "raspberry-pi-40pin" 会自动生成排针图和 26 个可操作 GPIO;单独的 [[pins]] 条目只能覆盖 profile 中已有 BCM 的名称、默认模式及安全状态,不能改变物理针脚映射或加入 BCM0/1。引脚编号统一采用 BCM。继电器板常见低电平触发,此时应设置 active_high = false,但必须以实际模块规格为准。
server_url 应填写 Host Server 的实际可达地址。若 Server 与 Client 同在一台树莓派上,并希望面板显示该树莓派的局域网 IP,也应填写其局域网地址(例如 192.168.10.190),不要填写 127.0.0.1;Host 会使用 WebSocket 对端地址作为节点 IP。
sudo apt install python3-gpiozero python3-lgpio
python3 -m venv --system-site-packages .venv
.venv/bin/pip install -e '.[pi]'
cp configs/pi-lab.toml.example client.toml
.venv/bin/hoa-client --config client.toml没有 GPIO 的开发机将 gpio_backend 改为 mock。Pi 5 应使用当前 Raspberry Pi OS;当前系统把 RP1 用户排针统一暴露为 gpiochip0,gpiozero 通过 lgpio 使用与 Pi 3/4 相同的 BCM 编号。生产部署可使用 deployment/hand-of-agents-client.service。该 unit 使用无登录权限的专用账号 hand-of-agents,不依赖安装人员或开发机的用户名。首次安装时创建账号并保护 Client 配置:
sudo useradd --system --user-group --home-dir /var/lib/hand-of-agents \
--create-home --shell /usr/sbin/nologin hand-of-agents
sudo install -d -o root -g hand-of-agents -m 0750 /etc/hand-of-agents
sudo install -o root -g hand-of-agents -m 0640 client.toml \
/etc/hand-of-agents/client.toml
sudo install -o root -g root -m 0644 deployment/hand-of-agents-client.service \
/etc/systemd/system/hand-of-agents-client.service
sudo systemctl daemon-reload
sudo systemctl enable --now hand-of-agents-client.service若 hand-of-agents 账号已由 Server 部署创建,则跳过 useradd。unit 通过 SupplementaryGroups=gpio 取得 GPIO 设备权限;不要把 Client token 放进 unit 或命令行。
当前部署使用 HOA_AUTH_MODE=none,以下请求无需 token。若切换为 api_key,给写操作及审计请求增加 X-API-Key 请求头。
# 节点状态
curl http://192.168.1.40:8000/api/v1/nodes
# 修改节点显示名;pi-lab 是内部 node_id,不会随显示名改变
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"name":"Bench Pi"}' \
http://192.168.1.40:8000/api/v1/nodes/pi-lab/name
# 将 GPIO17(物理 11 脚)配置为输出;初始化为 safe_state
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"action":"configure","direction":"output"}' \
http://192.168.1.40:8000/api/v1/nodes/pi-lab/pins/GPIO17
# 输出 HIGH
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"action":"set","value":true}' \
http://192.168.1.40:8000/api/v1/nodes/pi-lab/pins/GPIO17
# 持续输出 500ms HIGH / 500ms LOW;HIGH、LOW、释放或断线会停止循环
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"action":"pulse","duration_ms":500,"continuous":true}' \
http://192.168.1.40:8000/api/v1/nodes/pi-lab/pins/GPIO17
# 配置为带上拉的输入
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"action":"configure","direction":"input","pull":"up"}' \
http://192.168.1.40:8000/api/v1/nodes/pi-lab/pins/GPIO17
# 释放引脚,恢复 FREE 状态
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"action":"configure","direction":"unconfigured"}' \
http://192.168.1.40:8000/api/v1/nodes/pi-lab/pins/GPIO17
# 最近的审计事件
curl 'http://192.168.1.40:8000/api/v1/events?limit=50&node_id=pi-lab'节点名称接口接受 1–64 个字符,网页点击绿色名称即可重命名;api_key 模式下与其他写操作一样需要 X-API-Key。configure 支持 input、output、unconfigured,输入模式的 pull 支持 up、down、floating,并可通过 pulse_hz 保存该引脚的 PULSE 频率(0.1–10 Hz,默认 1 Hz)。重复确认相同的 Direction/Pull 不会重新初始化 GPIO;修改运行中 PULSE 的频率会保留当前模式和电平相位。高级弹窗会同步实际配置,Direction、Pull、频率及 HIGH/LOW/PULSE 均只在本地暂存,点击“确认”后才提交。set 接受布尔值,toggle 不需要额外字段。pulse 接受半周期 duration_ms;continuous=true 时持续循环,默认 false 时仅输出一次。网页仅在“高级”设置中编辑频率,Level 控件保持显示 PULSE,而引脚状态文字和颜色显示最近采样到的实际 HIGH/LOW。节点空闲时每 2 秒更新一次,存在连续 PULSE 时自适应为 500 ms。HTTP 2xx 代表树莓派已确认执行;节点离线返回 409,超时返回 504,非法引脚或模式错误返回 422。
Server 环境变量:
| 变量 | 说明 |
|---|---|
HOA_BIND_HOST / HOA_BIND_PORT |
监听地址与端口 |
HOA_AUTH_MODE |
none 免密钥操作;api_key 要求操作密钥 |
HOA_API_KEY |
Agent 和网页写操作密钥 |
HOA_NODE_TOKENS |
逗号分隔的 node-id:token 列表 |
HOA_DB_PATH |
SQLite 审计库路径 |
HOA_CORS_ORIGINS |
前端独立部署时允许的来源列表 |
HOA_COMMAND_TIMEOUT |
Server 等待 Client 确认的秒数 |
Client 的完整示例见 configs/pi-lab.toml.example。gpio_backend = "auto" 和 gpiozero 当前行为相同:GPIO 操作失败会明确返回错误,不会静默降级到 Mock。通用引脚默认 safe_state = false,为长期连接的继电器等设备添加覆盖配置,以明确触发极性和断线安全状态。时间间隔必须是有限正数,max_pulse_ms 必须为 50–60000,布尔配置必须使用 TOML 的 true/false,不能写成字符串。
PYTHONPATH=src python3 -m pytest -q
python3 -m compileall -q src tests实机测试日志放在 logs/,不要将 .env、Client token、SSH 密码或其他凭据提交到版本库。
