一、先说说现场的五个真实困扰
做过设备数据接入的人都遇到过这些:
现场还不能停。
测点数可能变成 8 个甚至另一组。传统做法是开两条连接、跑两个程序,
设备和网关两侧都要改。
只能在运行期试探,试错了就错位、丢帧。
监控画面迟迟不出数。
事后想做标定、追溯、二次换算都没有依据。
etherAdapter 就是为这五件事写的:一个进程、一套配置、一条 TCP 连接,
把设备数据原样汇入 EtherDB。
二、一条连接,两种采样:靠 frameType 区分
同一台设备用一条 TCP 连接上报两类数据:
- 状态帧(status):周期采样,一般 1Hz,测点固定;
- 事件帧(event):突发采样,频率更高、测点数可能更少或不同。
两者在6 字节自定义头的第一个字节里用帧类型区分:
[frameType 1B][flag 1B][frameLen 2B][sequenceId 2B][payload …][flag 1B?]
- frameType —— 这台设备上的"哪套测点"。对适配器来说,它就是另一台逻辑设备:
有自己的字段表、自己的解析规则、自己的 EtherDB 表。 - flag —— 事件流的定界字节。它只对事件帧有意义:在同一条 TCP 数据流里,
适配器靠它认出"接下来是另一套解析规则"。状态帧不带定界符,这个字节被忽略。 - frameLen —— 本帧 payload 字节数。一次可以携带多条记录
(frameLen / 单条记录长度),多个采样点共用一个头,省带宽。 - sequenceId —— 预留字段,当前不校验、不解析,将来扩展用。
配置上,同一 (ip, 端口) 写多行设备表即可:
| dev_T100_001 | 127.0.0.1 | 10002 | 100077 | 0xC(状态) |
| dev_T100_001_E | 127.0.0.1 | 10002 | 100078 | 0xE(事件) |
第一行是状态设备,其余是事件设备。设备侧只需要在头里改一个字节,
数据就分别落进 dev_T100_001 和 dev_T100_001_E 两张表。
这一步的价值:设备固件不用为"加一路事件"改造连接管理,
网关侧也不用新开监听端口。
三、状态数据不攒批:来了就提交
大多数状态量是 1Hz,根本不值得等。etherAdapter 的策略是:
- 状态帧:每收到一个数据块立即刷写,不攒批、不等定时器 —— 画面出数最快;
- 事件帧:按批写入 —— 突发事件频率高、点数多,批量写入才是吞吐最优。
两者在同一条管道里共存,配置里只需要把设备行写对。
四、只存原始值:factor 先放一边
时序库的价值在于"如实地留下现场",所以适配器不做物理量换算:
- data_proto_table.factor 当前不参与计算,启动时若发现配置了非 1 的系数,
只打一条告警提醒; - 整型字段就按原始码值存(有符号/无符号类型自动选合适的列宽),
FLOAT32/FLOAT64 按原值存; - 想换算?查询时再乘,或者交给上层 —— 原始值永远还在。
五、从设备到 EtherDB 的路径
设备 ──TCP──> muduo 网络层 ──无锁队列(moodycamel)──> 单线程解析
│
RowBatch ──┬──────────────┘
▼
写入队列 ──> 列绑定预编译 INSERT ──> EtherDB
└─> 发布队列 ──> ZMQ PUB(预留)
几个刻意的设计:
- 解析单线程:所有协议解析集中在一个线程里,配置驱动、没有锁竞争,
也避开了多线程解析的时序问题;网络线程只负责把字节搬进队列。 - 列绑定批量写入:按设备预编译 INSERT,每列一块连续内存,
一次绑定多行 → 一次执行。慢速 HTTP 数据走普通 SQL,不占预绑定资源。 - 配置驱动:三张 SQLite 表(设备 / 数据头 / 字段)决定一切,
加设备就是加一行,改协议就是改一行。
EtherDB 侧的性能已经有公开实测(见 EtherDB/docs/EtherDB-performance.md):
同机批量写入约 214 万行/秒、8 进程聚合约 618 万行/秒、
COUNT(*) 0.34 ms、千万行分页 0.82 ms,服务端单文件 1.07 MB。
适配器只负责"把数据正确地喂进去",不改变这条写入路径。
六、本机跑通的一条完整链路
用仓库自带的 fixtures,从设备模拟到落库实测:
csv2sqlite.exe config\\etherAdapter.db tests\\fixtures –force
etherdb_dserver.exe -p 7040
etherAdapter.exe
:: 状态 + 事件在一条 TCP 连接上交替上报
device_sim.exe -n 60 -i 5 -t C,E -l 73,62 -f 0,01 -e 0,03 -v 100
结果(query_check.exe 直接查库):
| dev_T100_001 | item1 = 100,102,104… | 状态帧,单字段,来了就提交 |
| dev_T100_001_E | item1 = 101,103,105…,含 item2 | 事件帧,两个测点,另一张表 |
| dev_MB_401 | reg1=2000+k, reg2=2100+k | MODBUS 3s 轮询,原始寄存器值 |
| dev_HTTP_001 | temp=12.5, humidity=60 | HTTP POST JSON,直拼 SQL |
运行统计里可以看到分流是干净的:frames +60 rows +60 (event +30) written +61 drop 0 err 0。
同一份 fixtures 还覆盖了:多记录共用一个头(device_sim -m 3)、
MODBUS 四种响应头(9/7/2/0)、HTTP 缺字段写 NULL、
以及 sequenceId 随便填也不影响解析。
七、适合谁用
- 现场设备不能改(或改不动),但要把数据接进时序库;
- 一台设备既要周期状态、又要突发事件,还不想开两条连接;
- 需要在边缘侧轻量部署(适配器与 EtherDB 都是单文件、无外部依赖);
- 数据要留原始值,标定/换算放在后面做。
八、小结
etherAdapter 做的是"最后一公里"里最脏的那段活:
把设备怎么说的话,原样、低延迟地翻译成 EtherDB 的列。
- 一条 TCP,两种采样 —— 靠 frameType;
- 状态数据来了就提交,事件数据按批落表;
- 配置即接入:三张表、一个进程。
设备不用动,数据库不用改,中间只多了一个进程。
网硕互联帮助中心



评论前必须登录!
注册