1. 从项目背景聊起:为什么偏偏是 MyEMS 加 Modbus TCP
先交代一下我这边的情况。手头负责的厂区有好几栋楼,配电房里电表品牌杂得很,有施耐德、有正泰、有安科瑞,还有一些国产小厂出的表。以前想统一看数据,只能每个品牌各自拉一套软件,或者让电工师傅每天抄表,效率低不说,数据还经常对不上。后来决定上一套能源管理系统,选型的时候在商业化平台和开源方案之间犹豫了很久,最后选了 MyEMS,原因不外乎三点:代码全开放、部署在自家服务器上数据不出内网、社区活跃文档也多。而接电表这件事,最稳妥、最通用的方式就是走 Modbus TCP。
为什么是 Modbus TCP 而不是 RS485?厂区里楼栋分散,最远的两栋楼之间直线距离超过两百米,RS485 走线麻烦、还要考虑终端电阻和屏蔽层,后期排查故障很费劲。Modbus TCP 的好处是以太网线就能搞定,现场交换机和网线都是现成的,电表端加一个串口服务器转以太网,或者直接用带网口的电表,网络打通之后数据就是纯 TCP 报文,跟数据库、服务器程序打交道非常顺。
MyEMS 这个平台本身对 Modbus TCP 的支持相当成熟,官方文档里就写了完整的接入流程,但说实话,光看文档是不够的。文档里讲的是理想情况,实际现场有一堆坑——寄存器地址映射对不上、字节序反了、数据类型选错、掉线重连机制没配好。这篇文章我就把从零到一、从硬件到数据库,完完整整做一遍的过程写出来,包括踩过的坑和排查思路,给后面要接的人一条能直接照抄的路。
适合看这篇内容的人:正在做能管平台选型的技术负责人、搞自动化集成的工程师、运维机房和厂区基础设施的 IT 人员。哪怕你是第一次接触 Modbus TCP,只要手里有一块带串口服务器的电表,按着步骤走也能把数据跑起来。
2. 智能电表与 Modbus TCP 通讯的基础盘:先说清楚数据是怎么传回来的
2.1 电表里那些寄存器到底是个什么东西
智能电表内部其实就是一个采集各种电气参数的微型计算机。电压、电流、功率、电量这些数值,在电表内部被存进一个个"寄存器"里。Modbus 协议做的核心事情就是:主站(比如你的采集服务器)向从站(电表)发一条指令,告诉它"我要读哪个地址的数据",从站收到之后把对应地址里的数据扔回来。
寄存器按功能分两类,一个是保持寄存器(Holding Register),可读可写,电表里的参数设置一般放这里;一个是输入寄存器(Input Register),只读,测量数据放这里。我们采集电量参数,绝大部分情况读输入寄存器就够了。每个寄存器是 16 位,也就是两个字节,但电表里的电压值往往超过 65535 的整数范围,所以实际读取的时候经常需要连续读两个甚至四个寄存器,拼起来才是一个完整的数值。
举个例子,某款电表的电压是 220.5V,它在数据手册里的寄存器地址是 0x0000,数据格式是"32 位 float,占 2 个寄存器"。那主站就要发一条命令:从地址 0x0000 开始读 2 个寄存器,拿回来四个字节,再按 IEEE 754 标准解成 float,最后得到 220.5。
2.2 一份靠谱的寄存器地址表是项目的地基
开始写采集程序之前,我建议你先花一个小时,老老实实把电表的数据手册翻一遍,把下面这个表格整理出来。这张表就是后面所有配置的依据,表错一个地址,数据就全错。
| 参数名 | 寄存器地址 | 数据类型 | 字节序 | 单位 | 倍率 |
|---|---|---|---|---|---|
| 三相电压(A/B/C) | 0x0000~0x0005 | 32位 float | AB CD | V | 1 |
| 三相电流(A/B/C) | 0x0006~0x000B | 32位 float | AB CD | A | 1 |
| 有功功率(总) | 0x0010 | 32位 float | AB CD | W | 1 |
| 正向有功电能 | 0x0040 | 64位 double | DC BA | kWh | 0.01 |
这张表里最坑的就是字节序。同样是四个字节 03 66 5C 2E,按 AB CD 解出来和按 CD AB 解出来完全是两个数。MyEMS 的 Modbus 协议适配层里,"Word Order"(字序)和"Byte Order"(字节序)这两个参数就是干这个用的。我自己的经验是:先拿一个已知的电压值(比如用万用表实测 220.5V),把几种字节序组合都试一遍,哪个解出来的结果和实测接近,哪个就是对的。
2.3 主站和从站请求的过程拆开看
Modbus TCP 报文头部有一个固定格式:事务处理标识符(2 字节)、协议标识符(2 字节)、长度(2 字节)、单元标识符(1 字节),然后才是功能码和数据。
读电表数据的报文大概是这样的:
- 事务处理标识符:01 00(每次请求递增,用来匹配响应)
- 协议标识符:00 00(Modbus 协议固定值)
- 长度:00 06(后面还有 6 个字节)
- 单元标识符:01(从站地址,也就是电表的地址,要和电表面板上设置的一致)
- 功能码:04(读输入寄存器)
- 起始地址:00 00
- 寄存器数量:00 06
电表收到之后,会回一条:
- 功能码:04
- 字节数:0C(12 个字节,也就是 6 个寄存器 × 2 字节)
- 数据:跟着 12 个字节的原始数据
这些细节,写采集程序的时候系统底层的 Modbus 库都帮你封装好了,但排查问题的时候必须能自己看懂报文。我遇到过好几次,数据读不出来,用 Modbus Poll 手动发同样的功能码却一切正常,最后发现是采集程序里单元标识符写错了,电表默认地址是 2,程序里写的是 1,自然被拒绝。
3. MyEMS 的数据接入配置实操:从配库到写采集指令的完整流程
3.1 先确认版本和环境,避免后面白忙一场
我用的是 MyEMS 4.x 版本,数据库是 MySQL 5.7,采集服务跑在 CentOS 7 上。这里提醒一下,MyEMS 的配置项在不同版本之间改过不少,尤其是数据库表结构和 Web 界面,旧版本的配置方法放到新版本上不一定完全兼容。所以做之前先执行一下myems-admin的登录,确认版本号。
环境层面需要准备好的几样东西:
- MySQL 数据库,建议 5.7 以上,MyEMS 的表结构用到了 InnoDB 和 JSON 字段,版本太低会有兼容性问题。
- MyEMS 的服务端应用,包含 myems-api(REST API)、myems-admin(管理界面)。
- 采集服务 myems-modbus-tcp,这是负责跟电表通讯的服务进程,也是本文配置的重头戏。
我是直接按照官方文档用 Docker Compose 方式部署的,比较省事。但注意,Docker 部署的话,网络模式要选 host 模式,因为采集服务要跟电表所处的局域网直接通信,bridge 模式容易出问题。
3.2 在 MyEMS 的数据库里创建电表设备并填写关键参数
MyEMS 的界面逻辑是:先建一个"数据源"(Source),再在数据源下面绑定"设备"(Equipment/Meter)。打开管理后台,左侧菜单找到"设备管理"或直接对应电表的类型,点新增。
你需要重点填的字段:
- 设备名称,比如"1号厂房总电表"
- 通讯协议:选择 Modbus TCP
- 从站地址(Slave ID):和电表面板上设置的地址保持一致,我这边统一规划,每栋楼配电房的电表从 1 开始往后排。
- IP 地址和端口:串口服务器的 IP 和数据端口,默认 502,有的设备是 1024 或别的端口,看手册。
保存之后,数据库中会生成一条设备记录,拿到这条记录的 ID,后面写采集指令的时候要引用。
3.3 配置采集指令:最重要的 "Point" 配置,一个错就全错
MyEMS 通过"点位"(Point)来定义采集项。每个点位绑定一个存储 ID(Data Table),指定对应的数据表,采集值会存到 MySQL 的对应列。
新建一个点位,下面几个选项必须理解透了再填:
名称:建议用英文或拼音,比如power_total、voltage_a,后面做报表和接口的时候调用方便。
存储类型:电表的电压、电流、功率这种实时变化的数据,选"瞬时量"(值不断覆盖);电能这种累加量,选"累计量"或者对应 electric energy 的类型,MyEMS 会有专门的电能统计逻辑。
Modbus 寄存器地址:这里注意,不同厂家手册里的地址标注方式不一样。有的手册写 40001 这种 Modicon 格式,有的直接写十六进制地址 0x0000。MyEMS 里填的是协议地址偏移量,也就是 0x0000 就填 0。如果你在手册里看到的是 40001,要换算成 0,规则是十进制减去 40001 再转十六进制。
数据类型:有 16 位无符号、16 位有符号、32 位浮点、32 位无符号等多种选项。电表的测量值我基本都用 32 位浮点,少数电能参数用 64 位双精度,注意跟手册保持一致。
倍率(Scale):电表内部原始值往往要乘一个系数才是真实工程值。比如某型号电能表原始值是 2456.78,对应实际 245678 kWh,那倍率就是 100,MyEMS 里填 100,平台上显示出来的就是真实电量。
这套东西第一次接触容易绕晕,我画了一个最小可用的配置示例:
| 配置项 | 示例值 | 说明 |
|---|---|---|
| 名称 | total_power | 总有功功率 |
| 协议 | Modbus TCP | 通讯方式 |
| 寄存器地址 | 16(十进制) | 对应手册 0x0010 |
| 数据类型 | 32 位浮点 | IEEE 754 单精度 |
| 字节序 | AB CD | 按手册要求 |
| 倍率 | 1 | 原始值即真实值 |
| 存储 ID | 对应能耗数据表 | 存到能源计量表 |
配置完成后,在 MyEMS 的后台界面刷新一下,正常情况下应该能在"实时数据"或"最新值"里看到这个点位的数值了。如果什么也没有,别急,先到下一步排查。
3.4 验证通信和数据的完整链路
数据通了没有,最简单的方法是看采集服务的日志。Docker Compose 部署的话,执行docker logs myems-modbus-tcp -f,能看到采集进程在跑请求和接收响应。
如果日志里压根没有请求发出去,那多半是点位配置没生效,检查点位是否启用、是否绑定了正确的设备。如果请求发出去了但是响应超时,那就是网络或者从站地址的问题,用ping先测一下设备的 IP 通不通,再用 Modbus Poll 手动请求同样的地址,看能不能拿到数据。
如果响应正常拿到了数据,但 MyEMS 界面上看不到值,排查思路集中在倍率和存储映射上面。把采集日志里收到的原始数据打印出来,自己手动按数据类型和字节序解一遍,看算出来的值和界面上显示的值是不是一致。不一致的话,就是字节序或者倍率填错了。
4. 硬件接线与通讯调试:串口服务器、网线和 Modbus Poll 配合三板斧
4.1 电表端的物理接线,这颗螺丝拧错就前功尽弃
Modbus TCP 是纯网络通讯,但大多数电表只有 RS485 接口,所以要加一个 RS485 转以太网的串口服务器。
接线的时候记住一条铁律:A 接 A,B 接 B,也就是电表的 RS485 A 端接串口服务器的 485 A 端,B 接 B。这一点很多新手会犯迷糊,有些厂家的接线端子标识是 A+/B-,有些是 D+/D-,总之先看电表说明书,别凭经验乱接。
串口服务器的参数也要跟电表保持一致,常见的配置是波特率 9600、数据位 8、无校验、停止位 1,也就是 9600 8N1。如果电表手册里写的是 4800,那串口服务器必须也改成 4800,两边不一致是绝对通讯不上的。
4.2 用 Modbus Poll 做一次"裸通讯测试",确认设备本身就是好的
数据接不上时,我的建议是先把 MyEMS 放一边,用 Modbus Poll 这个工具直接跟电表对一次话,它能帮你界定问题到底出在设备这边还是平台这边。
步骤很简单:
- 打开 Modbus Poll,新建连接,填上串口服务器的 IP 和端口 502。
- 从站地址填 1(或者你电表实际的地址)。
- 功能码选 04(读输入寄存器)。
- 起始地址填 0,寄存器数量填 10。
- 点连接,正常情况下右边表格会刷刷刷跳出数据。
如果这里就失败了,问题基本锁定在物理链路:网络不通、IP 填错、从站地址不对、波特率不一致。把这几项挨个检查一遍,直到 Modbus Poll 能正常读到数据,再回到 MyEMS 配置里去对比。
如果 Modbus Poll 能读到数据但 MyEMS 读不到,那就是配置问题。常见的情况是:地址填错(差一位)、数据类型不对(比如把 32 位浮点当成了 16 位整数)、倍率填错。把 Modbus Poll 读到的原始值记下来,手动解一遍,再对照 MyEMS 的显示值,就能定位到具体是哪一环出了偏差。
4.3 一个反直觉的"又读又写"问题
顺带提一个网上问得特别多的问题:Modbus TCP 怎么实现又读又写?
答案是:Modbus 协议本身不受限,只要功能码不同就能读能写。读一般用功能码 03(读保持寄存器)或 04(读输入寄存器),写一般用功能码 06(写单个保持寄存器)或 16(写多个保持寄存器)。在同一个 TCP 连接里,你可以分开发送读命令和写命令,Modbus 协议本身没有规定一个连接只能做一件事。
但在 MyEMS 这类数据采集平台里,采集服务主要用于读取(只读),平台方向不对电表下发写命令。如果你确实需要远程控制电表(比如下发费率、设置时段),通常有两种做法:一是直接用 Modbus Poll 或者写个小脚本手动发送写命令;二是在 MyEMS 里增加自定义控制扩展功能,走 Modbus TCP 的写操作指令。后者需要二次开发,不是开箱即用的功能,做项目前要评估好工作量。
另外有个容易踩的坑:某些串口服务器的虚拟串口模式下,同一时间只允许一个 TCP 客户端连接。也就是说,如果你 Modbus Poll 开着跟设备连着,MyEMS 的采集服务就连不上了,反过来也一样。排查问题的时候,记得把 Modbus Poll 断开再让 MyEMS 服务跑起来。
5. 典型问题排查链路:数据掉线、负数、寄存器错位,一次讲明白
5.1 采集进程在跑,MySQL 表里却是空值:从日志到数据表的完整定位
这个坑我印象特别深。有一天用户反馈说 2 号厂房的总电表数据断了,但采集服务日志显示一直在正常发请求。我打开 MyEMS 后台,发现那个设备的最新值时间确实停在昨天,然后开始排查。
第一步,先看采集服务的日志有没有报错。docker logs myems-modbus-tcp --tail 100,日志里干干净净,没有任何异常,说明采集进程认为自己活得好好的。
第二步,看数据库。登录 MySQL,找到存储电表数据的表,查一下最后几条记录:SELECT * FROM energy_meter_data ORDER BY id DESC LIMIT 10;结果发现,最近确实没有新数据写入,但奇怪的是,同一时刻跑着的另一块电表数据是正常的。
第三步,对比两块电表的配置。发现异常电表的寄存器地址写的是 0,而另一块写的是 1。去查手册才发现,这块表的输入寄存器起始地址就是 1,0 号地址根本不存在,设备直接返回了异常代码,而采集服务把异常当成"读不到数据"静默处理了,没产生错误日志。
这是我的一个经验教训:很多 Modbus 协议栈在收到异常响应的时候,不一定会主动打日志,尤其是那些配置成"连续轮询"的模式。你要么定期抽查数据库数据,要么在采集服务上开调试级别的日志,否则数据断了根本发现不了。
5.2 数据变成负数或者大得离谱:字节序和数据类型基本跑不掉
还有一次,一块电表的三相电流怎么读都是几万安培,显然不对。用万用表实测才 12.5A。
我拿回 Modbus 原始报文看了看,数据是41 48 00 00,按 IEEE 754 float 来解,41 48 00 00对应十进制就是 12.5,这没问题。问题出在 MyEMS 里配置的字节序写反了,解出来变成了一个天文数字。
MyEMS 里有一个"字节序"配置项,常见的是"AB CD"(大端)和"CD AB"(小端)。我那次填的是 CD AB,把 41 48 00 00 当成了 00 00 48 41 来解,结果自然不对。
解决思路很简单:先用 Modbus Poll 读到一组原始字节,手动按几种字节序都算一遍,看哪个和实际物理值对上。然后去 MyEMS 里把对应的点位配置改对,刷新即可。这个排查过程十分钟以内就能完成,前提是你要真的会手算 32 位浮点数——不会的话,网上找个在线 IEEE 754 转换工具也一样用。
5.3 设备偶尔断连,过一阵又自己恢复:超时和重试策略需要调整
多设备轮询时,轮询频率过高是很容易踩的雷。我在一台串口服务器后面串了 8 块电表,刚上线的时候配置成 1 秒轮询一次,结果没跑多久就掉线。原因很简单:Modbus 是半双工主从协议,8 块表串在同一根 485 总线上,从站处理指令需要时间,主站如果不管忙不忙,一直发请求,从站处理不过来就直接不回包了。
MyEMS 里控制扫描频率的参数是"轮询周期"或者点位采集间隔。我最后把间隔改成了 3 秒,加上开启失败重试机制,稳定跑了一个月没掉线。
如果你用的串口服务器带"多主机"或"自适应"功能,尽量关掉,这类功能在某些设备上反而会导致总线冲突。保持一主一从的干净拓扑,调试起来最简单。
5.4 网络层排查备忘
如果你的电表和 MyEMS 服务器在同一个局域网,Profinet/以太网通讯不通的时候,按下面顺序查一遍:
ping电表 IP,通不通。telnet 电表IP 502,看端口通不通。- 用 Modbus Poll 连一次,能否读到数据。
- 能读到数据,则检查 MyEMS 配置;读不到,回到物理层查接线和串口参数。
这个排查链路帮我解决过不下五次"平台读不到数据"的工单,每次都能把问题范围迅速缩小到具体某一层,非常值得养成习惯。
6. 进阶优化:点位组织、存储策略与数据可靠性
6.1 点位命名规范和分组,后期做报表能省一半时间
我强烈建议你在配置点位的阶段就做好命名规范。用中文名或者乱码式英文名,前期看起来很随意,后期做报表、对接 API、给领导展示的时候,全是坑。
我的命名规则是:
- 设备代号 + 参数类型,比如
b1_power_total表示 1 号厂房总有功功率 b1_voltage_a表示 1 号厂房 A 相电压- 统一用下划线分隔,不用点号和空格,方便 SQL 查询和脚本处理
MyEMS 还有一个"点位分组"功能,可以把同一批参数归到同一个组里,报表模块直接引用分组就能拉出整套数据。比如"1号厂房电量统计"分组下面挂 1 号厂房的所有电表点位,后面做日统计、月统计的时候非常方便。
6.2 存储周期与报表粒度,别把数据库撑爆也别让报表太粗
MyEMS 默认会把实时数据按采集周期写入数据库,但如果设备太多、点位太多,数据库的写入压力会很大,尤其是在做秒级采集的场景。
我的建议:
- 实时点位(瞬时量):采集周期 5~15 秒即可,存原始值。
- 累计量(电能):按分钟聚合,MyEMS 会自动做累计值差异计算,存增量。
- 报表统计:依赖底层原始数据,通过 MyEMS 自带的统计任务定时汇总,不用额外开发。
数据库表空间这块,我吃过亏。一开始没在意,半年不到,energy_meter_data这张表积累了上千万条记录,查询报表的时候明显变慢。后来设置了定时清理,把超过 90 天的原始数据归档到备份库或者导出到 CSV,报表库只保留近期数据,速度立刻回来了。
6.3 如果 MyEMS 自带功能满足不了你:二开接口的思路
MyEMS 开放了 REST API,你可以通过 HTTP 调用获取点位最新值、历史曲线、累计电量等,也可以自己写定时脚本往里头推送数据。最常见的二开需求是把采集的数据转发到第三方平台(比如厂里的 MES 系统或者云平台)。
我是这样做的:写了一个 Python 小服务,订阅 MyEMS 的数据库,每 30 秒读取一次最新数据,往上抛给 MES 的 HTTP 接口。关键点是要处理掉线重连和失败重发,别让数据丢了。整个过程大概几百行代码,不难,但要注意别直接把 MyEMS 的数据库只读账号暴露给外部系统,走 API 更安全。
7. 现场项目落地时的几个实际体会
文章写到这,主体流程已经全走完了。最后分享几个只有真正做完过项目才会明显感受到的点。
项目上线初期,一定要跟电工老师傅确认电表的互感器倍率。很多电表接的是电流互感器,200A/5A 这种,实际电流 = 读数 × 40,这个 40 倍的互感器倍率如果不乘进去,报表上的电流和功率全是错的。这个问题不在 Modbus 通讯层面,但绝对会影响你整个项目的可信度,别忽略。
另外,巡检记录很重要。我每个季度会让现场人员用手机拍一次电表屏幕上的读数,跟系统里的数值对比一下,确保长期运行数据没偏。系统再智能,也需要和物理世界对表的这层校验。
如果你团队里没人熟悉 Modbus 协议,建议先拿一块电表,配一台电脑,用 Modbus Poll 加上官方文档,完全搞懂一遍寄存器读取逻辑,再上来配置 MyEMS。磨刀不误砍柴工,协议底层通了,上层配置其实就是填表。
最后一个小技巧:MyEMS 还提供了自动生成 Excel 报表的功能,报表模板可以自定义。我把每天的能耗汇总报表定时凌晨生成,直接发到管理群,省了一句"今天用电多少"的人工汇报,连项目经理都说这功能加得好。
希望这篇从协议原理到现场落地的完整记录,能让你在对接 Modbus TCP 智能电表的路上少走几步弯路。项目里遇到的问题基本都能归结为地址、字节序、倍率、网络四件事,把这四件事弄明白,至少八成的问题你已经能自己搞定了。