开源设备管理平台OpenRig:解决开发板与硬件装备的混乱 前阵子清理工作台的时候我数了数手边的开发板、逻辑分析仪、树莓派、几台淘汰下来的迷你主机能通电的加在一起快二十台。最离谱的一次是我想找一个ESP32-S3的板子做测试翻了三个收纳箱才找到而另一块同型号的板子其实就插在旁边那台机器的USB hub上。设备管理这件事我踩了太多次坑最后实在忍不了才动手做了OpenRig这个开源装备管理平台。OpenRig这个名字起得很直白Open就是开源开放Rig在英文里就是一套设备、一台机器的意思合起来就是一套开放式的设备/装备管理平台。我把它定位成面向开发者、创客和硬件工程师的设备资产管理工具解决一个很多人忽略但每天都在消耗时间的问题当手头设备超过十几台之后你根本记不住谁在哪、状态如何、能不能用、属于哪个项目。这篇文章会完整拆解OpenRig的核心设计思路、功能模块和落地部署过程包括我踩过的真实坑。如果你也面临类似的设备混乱问题或者想自己搭一套轻量资产管理工具这套方案可以直接参考。1. 当开发者的装备多到记不住名字OpenRig的诞生背景先说清楚这个项目到底想解决什么不然你看后面的架构设计会觉得过度设计。1.1 我的设备混乱现场不只是找东西的问题我最初的痛点很简单设备太多找不到。但真把需求列清楚之后发现找东西只是最表面的问题。以我当时的真实场景为例手头设备大致分五类嵌入式开发板、测试测量仪器、迷你主机/工控机、网络设备、各种USB外设和传感器模块。每一类在管理上关注的点都不一样。比如说开发板我需要知道固件版本、串口占用情况、当前供电方式测试仪器需要知道校准日期、当前接线状态迷你主机需要知道IP地址、跑着什么服务、CPU温度是否异常。这些东西靠Excel表格根本维护不住因为设备状态是动态变化的而表格里的信息是静态的。再往下挖还有一个更麻烦的问题设备与项目的关联关系。我同时维护三四个项目有的设备在项目A里是核心硬件在项目B里只是辅助测试工具。设备用在哪个项目、谁在用、什么时候用这些信息一旦靠记忆维护基本两天就乱。1.2 市面上的方案为什么都不够顺手在动手写OpenRig之前我调研过现有的方案。商业的IT资产管理软件比如ServiceNow、Jira Asset Management功能很全但对个人开发者和小团队来说太重了部署一套要配置数据库、LDAP、审批流还没录入第一台设备人已经累了。开源的方案里GLPI和Snipe-IT是比较有名的资产管理系统Snipe-IT我实际部署过体验不错但它的核心模型是企业IT资产走的是采购单、公司部门、折旧逻辑。我需要的不是公司有几台笔记本而是这块开发板今天通没通电、现在连在哪个串口上。市面上还缺一个东西对设备的主动探测和控制能力。企业资产管理只管账面信息但我要的是一个能感知设备状态的平台——设备在线吗CPU温度多少能不能直接远程重启这些才是嵌入式开发和硬件调试真正需要的功能。所以我想要的工具是这样的轻量、可自托管、能录入设备静态信息、能采集设备动态状态、能通过API接入其他自动化流程、界面别太丑。市面上没有现成完全满足的组合OpenRig就是按这个需求清单设计的。2. 我把OpenRig拆成了三层架构设计与核心取舍OpenRig的架构没有搞微服务那套仍然是单体应用 轻量采集Agent但在逻辑上分成了三个清晰的层设备接入层、数据管理层、呈现与控制层。下面逐层讲我为什么这么设计。2.1 设备接入层Agent 主动探测的混合设计设备接入层解决的是数据怎么进来的问题。我一开始想过只用Agent主动上报也就是在每台需要管理的设备上跑一个小程序定时把状态 POST 给OpenRig服务器。这么做的好处是数据准确、可以采集非常细粒度的信息坏处是很多设备跑不了Agent——比如一台只有2MB Flash的单片机开发板、一台古老的逻辑分析仪、一台没有操作系统的网络交换机。所以最终采用了混合设计能跑Agent的设备树莓派、迷你主机、开发机上运行的VM安装轻量Python Agent负责采集进程级、系统级状态包括CPU、内存、磁盘、关键服务状态、温度传感器读数等。不能跑Agent的设备由OpenRig服务器端主动探测方式包括ICMP Ping、TCP端口探测、SNMP读取、mDNS查询。这类设备能拿到的数据没那么细但在线/离线是否响应某个端口这些关键信息已经覆盖了80%的需求。纯被动静态录入比如万用表、示波器、传感器模块这种没有网络接口的设备用二维码标签绑定资产编号扫码只能看静态信息不参与动态采集。这个分层很关键。如果你设计时只做Agent上报碰到老设备会很痛苦如果只做主动探测又拿不到进程级细节。混合设计让OpenRig的接入范围最大化从几百元的小模块到几万元的测试仪器都能纳入管理。2.2 数据管理层弱实体模型带来的扩展性最开始的版本我踩了个坑老老实实给每类设备建单独的数据表开发板一张表、服务器一张表、网络设备一张表字段各不相同。结果就是查询复杂、前端渲染复杂、加新设备类型要改表结构。后来我重构成了现在的弱实体模型。核心就三张表devices设备主信息、attributes扩展键值对、tags标签外加若干关联表。devices表只存所有设备通用的字段比如设备ID、名称、类型、厂商、序列号、MAC、IP、位置等每个设备类型特有的字段全部放进attributes用Key-Value方式存储。比如一块ESP32开发板可以有一个attributes记录{flash_size: 4MB}而一台示波器可以记录{bandwidth: 100MHz}。这个模型最大好处是扩展性新增设备类型不用改数据库结构前端动态渲染属性表单即可。查询上由于所有设备都在一张表里跨类型的组合搜索非常简单。比如我想找所有厂商是Raspberry Pi的、处于在线状态的、标签含main-lab的设备一条SQL就能解决不需要关联七八张表。代价是attributes查询性能会差一点但设备管理这个场景数据量通常几千条级别完全够用。2.3 呈现与控制层Web UI不多做把接口留足前端我用的是Vue 3 Element Plus后端FastAPI前后端通过REST API和WebSocket通信。UI功能保持在够用的范围仪表盘概览统计、设备列表表格过滤器、设备详情页静态信息实时状态操作按钮、标签管理、审计日志、系统设置。真正的核心不在UI而在API层。OpenRig对所有操作都暴露了REST接口比如注册设备、更新状态、触发控制动作、查询状态历史。这样你完全可以不用我的前端直接在你的自动化流程里调用API。举个实际例子我在CI/CD流水线里加了一步部署完成后调用OpenRig API把那台测试机的设备状态更新为已部署v1.2.3并且在WebSocket通道推送一条消息到前端大屏。这种设备平台作为基础设施的用法是商业资产管理软件很少考虑的场景。3. 核心功能逐个拆解登记、发现、监控、控制架构讲完落到具体功能。我按用户使用的动线来拆先把设备登记进来然后通过自动发现减少手工录入接着对设备做状态监控最后实现远程控制。这四步也对应了OpenRig的四大核心能力。3.1 设备登记一条资产记录需要存什么设备登记是所有功能的地基。我在设计字段时参考了企业资产管理的最佳实践但砍掉了折旧、采购审批这些不适合个人/小团队的流程。目前devices表的核心字段如下字段说明必填device_id系统生成唯一ID格式如DRV-20240513-001是name设备名称建议用型号用途命名如ESP32-S3-温度采集是device_type类型枚举dev_board/server/network/instrument/peripheral/other是vendor厂商否model型号否serial_no序列号用于防混淆否mac_addressMAC地址关联网络发现否ip_address管理IP可以是静态或DHCP否location物理位置如A柜-3层-左2否owner负责人/使用人否project_id关联项目否status可用/在用/维修/报废是purchase_date采购日期否notes备注否这里我强烈建议序列号和MAC地址一定要填哪怕你觉得用不到。序列号是用来区分同型号设备的唯一物理标识MAC则是后面网络发现和控制的基础。我统计过通过自动发现录入的设备里约70%是依靠MAC匹配到已有设备记录完成自动关联的。如果你录入时不填这两个字段后面自动化率会断崖式下降。3.2 局域网自动发现mDNS和UDP探测的组合拳设备登记如果全靠手填录入十几台设备就能把人劝退。OpenRig实现了基础但实用的局域网自动发现机制。第一种方式是mDNS。现在很多开发板、迷你主机默认启用了mDNS比如树莓派会广播raspberrypi.local。OpenRig后端通过扫描mDNS响应拿到设备的主机名和IP配合MAC地址就能自动创建设备草稿记录。第二种方式是UDP广播探测。我设计了一个简单的发现协议OpenRig服务器定期向局域网内发送一个UDP广播包包含项目标识和请求标识子网内所有安装了Agent的设备收到后会回复本机信息。这个机制不依赖mDNS网络环境复杂时更可控。发现到的新设备会进入待确认状态不会直接变成正式设备记录。原因很实际自动发现很可能搜到邻居家的设备或者某些临时接入的VLAN设备直接入库会产生大量垃圾数据。所有未确认的设备都会在Web UI里列表展示由管理员一键确认或忽略。这里要说一下为什么不做全自动入库安全和准确率。我初期版本是全自动的结果mDNS扫到了同一办公楼下其他团队的一台打印机还自动建了记录后来那台打印机变成离线状态导致我的仪表盘在线率数据一直很奇怪。加上手动确认这个环节后数据质量立刻上来了。3.3 状态采集不装Agent也能拿到基础数据状态采集分两套机制。对于安装了Agent的设备Agent每30秒采集一次系统状态包括CPU使用率、负载、内存、磁盘、网卡流量、关键服务状态、温度通过psutil以及/sys/class/thermal/thermal_zone*/temp读取然后通过HTTP POST到服务端。服务端写入时序数据库表device_metrics前端通过WebSocket实时刷新。采集间隔可以按设备类型配置对性能敏感的设备可以改成5秒一次对电池供电的IoT开发板可以改成5分钟一次省电也省带宽。对于没装Agent的设备OpenRig用ICMP Ping检测在线状态用TCP连接检测关键端口连通性。这里有个很实用的trick大多数网络设备虽然不支持安装Agent但都会开放HTTP管理端口、SSH端口或SNMP服务。我在设备详情页里开放了端口检测配置比如一台路由器你可以配置检测TCP 443和TCP 22只要这两个端口通就判定为业务可用状态。这样即便没有Agent也能实现比能ping通更精准的服务级监控。采集到的状态数据不只是用来显示OpenRig会根据阈值产生告警事件比如CPU温度超过85度、磁盘使用率超过90%、设备连续5分钟离线。告警可以通过WebHook推送到企业微信/钉钉/邮件我现在的项目通知配置的是飞书群机器人效果很满意。3.4 远程电源控制Wake-on-LAN和继电器控制状态监控发现问题之后最直接的需求就是远程处理而最粗暴有效的处理方式就是断电重启。OpenRig支持两种远程电源控制方式。第一种是Wake-on-LAN适用于支持网络唤醒的PC、迷你主机、服务器。原理很简单向目标设备的MAC地址发送魔术包Magic Packet设备网卡收到后在软关机状态下会唤醒整机。我在OpenRig后端实现了一个Python函数import socket import struct def send_wake_on_lan(mac_address: str): mac_bytes bytes.fromhex(mac_address.replace(:, ).replace(-, )) magic_packet b\xff * 6 mac_bytes * 16 with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.sendto(magic_packet, (255.255.255.255, 9))代码不复杂但有几个细节容易踩坑目标设备必须开启网卡的Wake-on-LAN功能BIOS/UEFI里打开Windows设备管理器里勾选允许此设备唤醒计算机而且魔术包通常只能在设备所在局域网内发送成功跨网段需要配置广播代理。我这里偷了个懒把Wake-on-LAN按钮只暴露给同一二层网络内的设备跨网段的场景留给Agent处理。第二种方式是通过串口/网络继电器控制。有一类设备如老式测试仪器、没有网卡的嵌入式设备无法通过网络唤醒我给OpenRig加了一个继电器执行器的概念你可以接入一个支持HTTP控制的智能继电器比如ESP8266做的8路继电器板在OpenRig里配置继电器通道与设备的映射关系。这样在设备详情页点重启OpenRig会先发命令给继电器断开对应通道电源等3秒后再闭合。这个方法对老设备特别有效我现在实验室的几台老示波器就是通过这种方式统一管理的。4. 从零搭建OpenRig可以直接抄的部署步骤下面进入实操环节。我的部署环境是Ubuntu 22.04服务器4核8GB内存。整个系统用Docker Compose管理代码主要分三个部分FastAPI后端、Vue前端、Agent采集端。4.1 后端选型为什么是FastAPI而不是Django我最早其实用Django写过一版写管理后台很爽但我很快就烦了。设备管理这种应用核心是接口交互不是管理后台Django的ORM和Admin虽然成熟但数据模型经常变每次改模型都要做迁移。FastAPI在这方面的体验好得多自带OpenAPI文档前端联调时直接看swagger页面就行基于Pydantic做参数校验写起来干净性能也比Django高异步支持更顺手。如果你做的也是这种数据量不大但接口交互多、模型变化频繁的中小型内部系统我建议选FastAPI。后端目录结构大致是这样的openrig/ ├── app/ │ ├── main.py # FastAPI入口 │ ├── models.py # SQLAlchemy数据模型 │ ├── schemas.py # Pydantic请求/响应模型 │ ├── api/ │ │ ├── devices.py # 设备管理接口 │ │ ├── discovery.py # 自动发现接口 │ │ ├── metrics.py # 状态数据接口 │ │ └── control.py # 控制操作接口 │ ├── core/ │ │ ├── config.py # 配置读取 │ │ ├── security.py # 认证与权限 │ │ └── discovery.py # mDNS/UDP探测逻辑 │ └── services/ │ ├── agent_receiver.py # Agent上报接收 │ └── alerting.py # 告警规则引擎 ├── agent/ │ ├── openrig_agent.py # Agent主程序 │ └── collectors.py # 采集逻辑 ├── frontend/ # Vue3项目 ├── docker-compose.yml └── requirements.txt4.2 设备Agent的采集脚本设计Agent我用纯Python写不依赖第三方框架方便直接拷到嵌入式Linux设备上运行。核心采集逻辑用psutil读取系统信息这部分代码量不大import psutil import socket import platform def collect_basic_metrics(): cpu_percent psutil.cpu_percent(interval1) memory psutil.virtual_memory() temp None try: with open(/sys/class/thermal/thermal_zone0/temp, r) as f: temp round(int(f.read().strip()) / 1000.0, 1) except FileNotFoundError: pass return { hostname: socket.gethostname(), platform: platform.platform(), cpu_percent: cpu_percent, memory_percent: memory.percent, disk_percent: psutil.disk_usage(/).percent, temperature_c: temp, mac: get_mac_address(), timestamp: int(time.time()), }采集到之后Agent通过HTTP POST上报curl -X POST http://openrig-server:8000/api/v1/agent/report \ -H Authorization: Bearer AGENT_TOKEN \ -H Content-Type: application/json \ -d JSON_DATA上报频率默认30秒一次。如果上报接口失败Agent会在本地缓存最近10条待网络恢复后补报避免因短暂网络抖动丢失重要状态数据。4.3 Docker Compose一键部署部署OpenRig整个后端前端我全部用Docker Compose编排核心是两个服务apiFastAPI和webNginx托管Vue构建产物。SQLite直接挂载volume持久化数据量大了可以平滑切到PostgreSQL。version: 3.8 services: api: build: ./app container_name: openrig-api volumes: - ./data:/data - ./config:/config environment: - DATABASE_PATH/data/openrig.db - SECRET_KEY${SECRET_KEY} - AGENT_TOKEN${AGENT_TOKEN} ports: - 8000:8000 restart: unless-stopped web: build: ./frontend container_name: openrig-web depends_on: - api ports: - 8080:80 restart: unless-stopped部署步骤很简单克隆代码仓库复制.env.example为.env修改SECRET_KEY和AGENT_TOKEN。执行docker compose up -d --build。等待构建完成访问http://服务器IP:8080初始化管理员账号。我建议把AGENT_TOKEN设置得复杂一点因为Agent通过这个token上报数据如果太简单容易被局域网内其他设备扫到后伪造上报数据。4.4 数据模型与API接口示例核心数据模型用SQLAlchemy定义简化版如下class Device(Base): __tablename__ devices id Column(Integer, primary_keyTrue) device_id Column(String, uniqueTrue, indexTrue) name Column(String, indexTrue) device_type Column(String) vendor Column(String) model Column(String) serial_no Column(String, indexTrue) mac_address Column(String, indexTrue) ip_address Column(String) location Column(String) owner Column(String) status Column(String, defaultavailable) is_confirmed Column(Boolean, defaultFalse) created_at Column(DateTime, defaultdatetime.utcnow) updated_at Column(DateTime, defaultdatetime.utcnow, onupdatedatetime.utcnow) class DeviceAttribute(Base): __tablename__ device_attributes id Column(Integer, primary_keyTrue) device_id Column(Integer, ForeignKey(devices.id)) key Column(String) value Column(String) updated_at Column(DateTime, defaultdatetime.utcnow, onupdatedatetime.utcnow)API方面最常用的是这几个接口GET /api/v1/devices # 设备列表支持分页/搜索/过滤器 POST /api/v1/devices # 创建设备 GET /api/v1/devices/{device_id} # 设备详情 PATCH /api/v1/devices/{device_id} # 更新设备信息 POST /api/v1/devices/{device_id}/wake # 触发Wake-on-LAN POST /api/v1/devices/{device_id}/relay# 触发继电器控制 GET /api/v1/devices/{device_id}/metrics # 获取状态历史 POST /api/v1/agent/report # Agent上报状态你可以直接用curl或者Postman调试这些接口。举个例子录入一台新的树莓派curl -X POST http://localhost:8000/api/v1/devices \ -H Authorization: Bearer ADMIN_TOKEN \ -H Content-Type: application/json \ -d { name: rpi4b-test-01, device_type: dev_board, vendor: Raspberry Pi, model: 4B, serial_no: 123456789, mac_address: dc:a6:32:aa:bb:cc, ip_address: 192.168.1.120, location: A柜-2层, owner: zhangsan, status: in_use, attributes: {memory_gb: 4, storage_sd: 16GB} }这些接口的设计参考了Snipe-IT的REST API风格如果你之前用过Snipe-IT上手OpenRig会非常快基本命令和字段都是相通的。5. 我在真实机房环境中踩过的坑OpenRig开发到现在有半年多其中有几个坑花了我大量时间排查这里分享出来希望你能绕过。5.1 设备发现的广播边界问题第一个版本我以为mDNS和UDP广播能发现所有局域网设备结果在真实的办公室环境里翻车了。办公室网络分了多个VLAN广播包只能在一个二层网络里传播跨VLAN的设备完全发现不了。我一度以为是自己代码写错了百思不得其解。后来查资料才明白广播发现机制受限于广播域这是原理性限制不是代码bug。解决方案有两个一是给OpenRig配置多个网段扫描任务每个子网单独探测二是在关键网段各部署一个Agent由Agent作为中继上报该网段的设备信息。这两种方式可以共存我在配置里加了站点site和网段subnet的概念每个站点可以绑定多个网段自动发现任务按网段维度独立运行。这样多办公室、多VLAN的场景就能覆盖到了。5.2 权限分级不能偷懒从只读API到操作审计最开始OpenRig只有一个管理员账号和一堆Agent token我觉得内部工具没必要做复杂的权限管理。结果有次一位同事帮我测试新设备我给了他管理员权限他全选删了一批历史测试设备记录。虽然数据有备份但那次之后我痛定思痛加了三层权限模型访客viewer只读所有设备的静态信息和状态不能做任何操作。操作员operator可以新增/编辑设备可以触发重启和远程控制但不能删除设备不能修改系统配置。管理员admin拥有所有权限可以管理用户、删除设备、修改系统参数。同时所有控制类操作Wake-on-LAN、继电器动作、设备删除都会写入审计日志记录操作人、操作时间、目标设备、操作结果。这个设计我们在实际使用中觉得很必要——尤其在多团队共用一套OpenRig的情况下你不可能给每个人都无限权限。5.3 数据量增长后的查询性能问题OpenRig刚上线时只有几十台设备查询非常流畅。等到设备数量突破两千台状态数据表每天增加十几万条之后问题就出现了。设备列表页默认查询最近24小时内在线设备结果响应从原来的毫秒级变成了秒级前端转圈圈。排查后发现瓶颈有几个叠加第一device_metrics表没有按时间建索引导致范围查询全表扫描第二后台自动发现任务每5分钟全量扫描一次局域网扫描结果和现有设备做全表匹配耗时太长第三SQLite默认单写模式并发一高就锁表。解决方案是分三步走的给device_metrics表加了时间列索引并用分区表按月分区这个改动解决了80%的查询延迟问题。自动发现任务改成增量扫描用上次扫描的MAC地址集合做缓存只对新增MAC发起匹配查询。数据量继续增长后我把数据库切到了PostgreSQL启用了连接池和更合理的索引。说实话如果一开始就用PostgreSQL后面这些迁移成本会小很多。我的建议是如果你预计设备会超过500台或者状态数据超过100万条直接上PostgreSQL别用SQLite。5.4 部署版本兼容性Python版本和依赖锁这个坑很细小但很折磨人。某个Agent跑在一台很老的Debian设备上系统Python版本是3.6而Agent代码用了f-string的嵌套引号Python 3.12才有的语法导致Agent一启动就报语法错误。我远程排查了半天最后才发现是Python版本语法兼容问题。另外一次我在开发机上正常更新了psutil依赖到最新版结果部署到一台ARM设备上发现psutil的新版本没有对应平台的预编译wheel源码编译又缺头文件折腾了很久。现在我把教训固化到了项目规范里Agent代码严格限制Python 3.7以上可运行的语法不用太新的语言特性。requirements.txt锁定所有依赖的精确版本并用pip freeze requirements.lock生成锁定文件。Agent部署文档明确要求先在设备上执行python3 --version小于3.7的直接劝退用旧版Agent镜像。兼容性问题的核心不是代码多漂亮而是你在什么环境下运行。尤其嵌入式设备Python环境千奇百怪提前做好版本约束能节省大量排查时间。实际使用后的几点体会OpenRig从最初的一个想法到现在稳定运行我最大的感受是设备管理工具的价值不在于界面多花哨而在于它能不能真正变成你工作流的基础设施。我现在每天打开电脑第一件事不是翻微信记录找IP地址而是瞄一眼OpenRig仪表盘上哪些设备在线、哪些设备告警。遇到测试环境出问题远程重启一下设备就能解决大半不用再往实验室跑了。如果你也要做类似项目我的建议是第一别追求大而全先解决设备清单在线状态基础控制这三件事用起来之后再迭代第二从第一天就设计好开放API因为你不知道以后会接入什么自动化流程第三权限和审计日志不要觉得早一旦设备被误删再补就晚了。OpenRig这个项目目前还在持续迭代中下一步我准备加入设备借用/归还的管理流程以及更丰富的告警策略配置。如果你有类似设备管理的困扰不妨也动手搭一套适合自己的设备管理这种小事理顺之后省下的时间远超你的想象。