尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

CJ/T188协议详解:从帧结构到水表抄读实例

发布时间:2026/9/29 7:18:15

资讯中心
01
ARTICLE

CJ/T188协议详解:从帧结构到水表抄读实例

CJ/T188协议详解:从帧结构到水表抄读实例
做智能水表、智能燃气表接入的朋友迟早会跟CJ/T188协议打交道。我第一次接触这个协议是在一个水务项目的采集器适配阶段手里只有一份扫描得歪歪扭扭的厂家协议文档帧里全是十六进制字节网上能搜到的资料又少又零散。后来啃完标准、又拿真实表计来回测了大半个月才把组帧、拆帧、校验这些环节彻底吃透。这篇不是标准原文的复读而是把CJ/T188协议的核心结构和实际调试经验一次性说清楚重点配合一个读取水表累计流量的完整实例适合要做采集器、集中器、上位机或者平台接入的开发者参考。1. 协议整体设计与思路拆解1.1 为什么会出现CJ/T188协议水表、燃气表从纯机械表升级到带数据输出的智能表最早一批厂家都是各做各的私有协议。麻烦很快出现一个县城可能同时用好几个品牌的表计水务公司上一套抄表系统得给每个品牌单独写一套驱动采集器厂商也得跟着适配N种帧格式。CJ/T188就是在这种背景下被提出来的户用计量仪表数据传输技术条件。它的核心思路是把水表、燃气表、热量表这类户用计量仪表的通信帧格式统一起来。帧里用专门的字段区分仪表类型水表、燃气表、热量表走同一套帧骨架只是数据标识和部分业务命令不同。这样采集端只需要实现一套帧解析框架再按仪表类型去匹配不同的数据标识即可集成成本明显降低。这个思路今天看不算稀奇但在表计行业里算是关键一步。现在很多智慧水务、智慧燃气项目底层通信依然大量沿用这套帧结构只是承载的物理链路从RS-485换成了M-Bus、LoRa、NB-IoT等。协议本身没变变的只是运输工具。另外需要提醒的是CJ/T188自己也经历了版本更新不少老项目里的表计还是按早期版本实现的新开发上位机时最好先确认目标表计支持的是哪个版本不要拿着新版标准硬套旧设备。1.2 协议在通信链路中的位置理解CJ/T188先要分清它属于协议栈里的哪一层。它是一个应用层协议只管报文长什么样、每个字段什么意思不关心数据到底通过什么物理链路传输。实际项目中最常见的组合是RS-485总线加CJ/T188。采集器通过RS-485接到一排水表轮询每个表号抄完一个再抄下一个。换成M-Bus时物理层换成两线制总线供电通信但采集器组出来的帧还是CJ/T188。到了NB-IoT表计帧结构同样不变只是把完整报文作为应用层负载通过运营商网络传到平台端平台侧需要做的第一件事依然是把报文里的CJ/T188帧解出来再解析表底数、状态等参数。这对开发者的启示是协议适配和物理链路可以分开处理。不管前面接的是RS-485、M-Bus还是无线模块只要能把字节流完整收上来后面的帧解析逻辑完全可以复用。我见过不少人一开始把注意力全部放在网络链路上结果发现报文通了却解不出数据问题反而出在帧解析上。1.3 和DL/T645的关系做过电表接入的人看到CJ/T188多半会觉得眼熟帧起始符68H、地址域、控制码、数据长度、数据域、校验码、结束符16H这个骨架和电力行业DL/T645非常像。CJ/T188在设计时确实参考了类似的帧结构思路可以说是表计行业里的“同门师兄”。区别主要在于业务部分。DL/T645面向电能表数据标识、命令集、费率结构都是按电力计费需求设计的CJ/T188面向水表、燃气表、热量表数据标识和命令集围绕累计流量、瞬时流量、阀门控制、结算日数据这些业务展开控制码的约定和DL/T645也不完全一样。所以如果你之前做过DL/T645或Modbus转CJ/T188会很快。重点是放下“以为协议都差不多”的惯性把控制码、数据标识、地址域填充规则这些差异点单独拎出来逐个对着标准确认。后面我会专门讲这几个容易踩坑的字段。2. 帧结构、控制码与校验规则拆解2.1 帧结构逐字段拆解CJ/T188的完整报文可以分为请求帧和应答帧但骨架是一样的。一个完整帧不含前导字节的结构如下字段长度说明帧起始符1字节固定68H仪表类型1字节10H水表、20H燃气表、30H热量表等地址域7字节从站地址通常包含地区编码、用户地址等不同厂家填充规则差异大控制码1字节命令类型与传输方向数据长度1字节数据域的字节数用十六进制表示数据域可变数据标识加具体数据校验码1字节从68H到数据域最后一字节的累加和取低8位结束符1字节固定16H发送时主站通常会在帧最前面加几个前导字节常见的是FE FE FE。前导字节的作用是唤醒接收方、让接收电路完成同步避免串口刚开始收数时出现丢字节。接收方在解析时可以直接跳过前导从68H开始找帧头。这里要特别提醒地址域7个字节是实际项目里差异最大的地方。标准虽然划分了地区编码、用户地址等分段但厂家实现时经常按自己的规则来有的把7字节直接当表号用有的是低字节在前有的高字节在前最后一位可能固定是00也可能是厂家代码。拿到新表计第一件事必须是查它的协议文档把地址域的字节顺序和填充规则确认清楚否则后面组帧全是白搭。2.2 控制码命令与应答的约定控制码字段看似只有1个字节其实每个bit都有讲究。最高位表示传输方向0表示主站下发的命令帧1表示从站发出的应答帧。次高位表示应答状态0为正常应答1为异常应答。再往下一bit表示是否还有后续帧用于大数据量分帧传输低5位是功能码。以最常见的读测量值为例主站下发控制码是00H从站正常应答是81H异常应答是C1H。收到81H说明表计正常返回了业务数据收到C1H说明表计收到了命令但认为内容有问题比如地址不匹配、数据标识不支持。实际工程中很多采集程序只判断了81H没有区分C1H导致表计明明在线却始终解不出数据排查半天才发现是异常应答帧被当成了正常数据处理。除了读测量值标准里还定义了读状态、读地址、写参数、广播校时等命令命令码需要按标准取值同时部分厂家会在标准基础上做扩展命令。我的建议是开发前把目标表计厂家协议文档里的控制码定义表完整抄一份放到代码注释里不要只记一个“读测量值00H”就上手。2.3 数据标识与BCD编码数据标识是数据域里的核心字段用两字节表示一般写作DI0和DI1。DI0通常用来表达数据类型和存储格式DI1用来表达具体测量项。水表读累计流量时很多厂家用的是90 1F这个组合返回数据时数据域里会先回填数据标识再跟上具体数据。不过数据标识并不是全行业完全统一。不同厂家、不同仪表类型之间数据标识的定义可能存在差异哪怕是同一个“累计流量”有的表用90 1F有的用90 1D还有的在后面多一个命令序号字段。处理办法很简单以厂家协议文档里的“数据标识定义表”为准开发时把需要用到的标识做成配置表不要硬编码死在代码里。再看数据编码。水气表返回的表底数、时间、压力值绝大多数都采用BCD编码也就是1个字节表示两位十进制数。比如12H 34H表示十进制12340AH表示10。表底数通常用多个连续BCD字节表示高位不足时补00。读取后要结合协议约定的小数位换算成实际值比如返回的BCD数值是1234567协议规定该表底数是3位小数那实际累计流量就是1234.567立方米。小数位通常与表计的量程、精度设置有关必须在系统配置里固定下来不能靠猜。2.4 校验和计算方法CJ/T188的校验码是累加和算法不复杂但特别容易算错。正确的计算范围是从帧起始符68H开始到数据域最后一个字节为止把这些字节全部相加超过一字节的部分直接丢弃只保留低8位。前导字节FE、结束符16H都不参与计算。举个例子请求帧68 10 12 34 56 78 90 12 00 00 03 90 1F 00把这14个字节逐一相加最后结果低8位是E0H这个E0H就填在校验位。我在第3章的实例里会把这个计算过程完整走一遍。容易出错的地方主要是三个一是把前导字节也算进去了导致校验码对不上二是把结束符16H也算进去了三是只加了数据域或只加了地址域没有按“68H开始到数据域末尾”的完整范围来算。还有极少数厂家会在标准基础上改用CRC校验甚至把校验规则改成包含前导遇到表计不应答时可以优先怀疑一下校验规则是否真的按标准来。3. 实例读取水表累计流量的完整过程3.1 准备阶段确认表计参数以一个常见的485接口水表为例。要成功抄读先确认四个参数仪表类型、表号/地址、波特率、数据标识。本例水表仪表类型是10H表号123456789012厂家协议文档规定地址域按字节12 34 56 78 90 12 00填充串口参数为9600、8数据位、无校验、1停止位读累计流量的数据标识为90 1F。这些参数看着简单但每一项都关系到能不能抄回正确数据。尤其是地址域填充格式不同厂家五花八门有的把表号反过来填有的最后一位填表类型还有的需要在前面补0。文档里没有写清楚的话最好用厂家的配置工具或者直接咨询表计厂商确认不要想当然。3.2 组帧构造读表请求按照第2章的结构读表请求帧可以组织为FE FE FE 68 10 12 34 56 78 90 12 00 00 03 90 1F 00 E0 16逐字段拆开看FE FE FE前导字节68H帧起始符10H仪表类型水表12 34 56 78 90 12 00地址域7字节00H控制码读测量值03H数据长度数据域共3字节90 1F 00数据域90 1F为读累计流量数据标识00为命令序号E0H校验码16H结束符这个帧里前导字节不一定必须是3个有些厂家要求2个有些需要发送多个FE才唤醒具体看表计文档。发送时注意表计可能需要几十毫秒的唤醒时间不要在紧接着就发请求否则表计还没就绪可能直接丢掉第一帧。3.3 用Python脚本自动组帧与解析手写组帧效率低而且容易算错校验码。实际项目中我习惯用脚本把组帧和校验封装起来既能快速验证也能在调试时直接复用。下面这个Python函数可以根据表号自动生成读表请求帧def make_read_request(addr_bytes, meter_type0x10): addr_bytes: 地址域7字节list或bytes meter_type: 仪表类型水表默认0x10 返回完整的读表请求帧含前导FE FE FE frame bytearray() frame b\xFE\xFE\xFE # 前导字节 frame.append(0x68) # 帧起始符 frame.append(meter_type) # 仪表类型 frame bytes(addr_bytes) # 地址域 frame.append(0x00) # 控制码读测量值 data bytes([0x90, 0x1F, 0x00]) # 数据标识读累计流量 frame.append(len(data)) # 数据长度 frame data # 数据域 cs sum(frame[3:]) 0xFF # 从68H开始累加取低8位 frame.append(cs) # 校验码 frame.append(0x16) # 结束符 return bytes(frame)调用方式addr [0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x00] frame make_read_request(addr) print(frame.hex( ).upper())输出FE FE FE 68 10 12 34 56 78 90 12 00 00 03 90 1F 00 E0 16注意这里sum(frame[3:])的范围frame的前3个字节是FE FE FE从索引3开始正好就是68H直到数据域末尾和协议要求的校验范围一致。如果你的组帧函数加了前导或调整了字段顺序校验范围一定要跟着改。解析返回帧同样可以写成函数。返回帧比请求帧多了从站应答状态位和数据长度变化解析时注意用帧尾16H辅助定位不要只靠查找68H因为数据域里有可能出现和68H相同的字节。def parse_response(buf): # 从后往前找结束符向前定位帧起点 end buf.rfind(b\x16) if end 0: raise ValueError(未找到结束符16H) # 从结束符往前找帧起始符68H start buf.rfind(b\x68, 0, end) if start 0: raise ValueError(未找到帧起始符68H) frame buf[start:end] if frame[1] ! 0x10: raise ValueError(f仪表类型不符: 0x{frame[1]:02X}) length frame[10] data frame[11:11 length] cs frame[11 length] if (sum(frame[:-1]) 0xFF) ! cs: raise ValueError(校验码错误) ctrl frame[9] if ctrl 0x81: return (ok, data) if ctrl 0xC1: return (error, data) return (unknown, data)3.4 解析返回帧读出表底数把上面组好的请求帧通过串口发出去正常应答帧如下68 10 12 34 56 78 90 12 00 81 0B 90 1F 00 00 00 00 00 01 23 45 67 39 16逐段拆解68H帧起始符10H仪表类型水表12 34 56 78 90 12 00地址域与请求一致81H控制码从站正常应答0BH数据长度数据域共11字节90 1F 00数据标识和命令序号与请求对应00 00 00 00 01 23 45 67表底数BCD码BCD数值为123456739H校验码16H结束符假设该表计协议规定累计流量为3位小数则实际累计流量为1234.567立方米。换算逻辑就是把BCD字节展开成整数再除以10的小数位次方。小数位这个参数一定要从厂家文档确认常见水表有2位、3位、4位小数几种配置错一位数据就放大了10倍现场最容易出这种“看着能解析但数值明显不对”的怪问题。4. 现场调试常见问题与排查技巧4.1 高频问题速查表现象可能原因处理办法完全无应答接线错误、波特率不匹配、地址不对、表计未上电先用示波器或万用表确认信号再核对串口参数有应答但校验错校验范围算错、前导字节被纳入计算按“68H到数据域末尾”重新累加能解析但数值离谱BCD高低位颠倒、小数位配置错、数据标识选错对照厂家文档确认字节顺序和小数位表计时而应答时而不应前导唤醒不足、总线上多从站地址冲突增加前导字节检查地址唯一性返回帧里找不到68H数据域里出现和68H相同的字节定位逻辑错误从结束符16H向前定位或用厂家抓包对照4.2 无应答问题的排查思路无应答是表计调试里最常见的问题我的排查顺序是先物理层再链路层最后应用层。物理层先确认485的A/B线有没有接反表计是否正常上电总线末端匹配电阻是否需要。用串口工具发一组固定字节比如55 AA看是否能收到回显。硬件问题不解决后面所有帧解析都是白搭。链路层看波特率、数据位、校验位、停止位是否和表计一致尤其注意校验位。很多表计出厂默认无校验也有少数默认偶校验两边对不上时报文会时不时出现乱码。应用层就是逐个字段核对仪表类型、地址域、控制码、数据标识、校验码。这里有一个经验先在厂家配置工具里用相同参数抄一次表确认表计本身工作正常再用自己的工具发同一帧如果厂家工具能抄到而你的不能逐字节对比两帧差异问题很快就暴露了。4.3 数据乱码与解析错误帧能收到、校验也通过但解析出来的数据不对通常集中在三个方面。第一是BCD高低位顺序。有的表计表底数按从低位到高位的顺序发送有的按从高位到低位甚至同一个表的不同数据项顺序都可能不同。解析代码里要根据数据标识分别处理不要统一套一个格式。第二是小数位。前面说过小数位配置错会带来10倍的偏差这类问题在界面上不会显示“解析失败”而是给你一个看起来正常但完全不对的数值最难发现。建议在系统配置里把小数位、单位、量纲做成字段和表计型号绑定。第三是数据标识不匹配。同一张表可能有多个累计流量比如当前累计流量、结算日累计流量、上期结算累计流量数据标识各不相同。如果抄错标识可能把结算日数据当成当前流量用了。写采集程序时务必把每个数据标识的业务含义注释清楚。4.4 我的调试工具与习惯调试CJ/T188协议我一般准备三样东西一个支持HEX发送和接收的串口调试助手、一个USB转RS-485模块、一个厂家提供的表计配置工具。串口助手负责快速验证帧格式对不对厂家工具负责提供“标准答案”。我自己的习惯是先在串口助手里手工把请求帧发出去把返回帧贴下来保存作为第一份现场报文记录。然后再写脚本、写程序每改一次逻辑都和这份手工报文做对照。这样即使后面程序里出了bug也知道问题出在组帧、校验还是解析不用来回猜。另外多个表计产品做兼容时建议整理一份Excel对照表把每个厂家的地址域格式、控制码、数据标识、小数位、波特率都列出来。这个表看着简单实际是协议适配项目的核心资产能省掉后面大量的重复沟通成本。我个人在实际项目中体会最深的一点是CJ/T188本身并不复杂复杂的永远是各厂家的“实现差异”。接到一台新表计先查协议文档、再用厂家工具抓标准报文、最后拿真实设备离线验证这三步走完后面的开发基本就是填表活。把这个流程沉淀成自己的标准动作比背下所有协议细节更管用。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。