拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Java的智能电表采集系统:DL/T645协议与串口通信实战解析

基于Java的智能电表采集系统:DL/T645协议与串口通信实战解析 简介这是一份基于Java编写的智能电表采集系统毕业设计项目源码包面向计算机、软件工程、电气自动化等专业学生可作为毕业设计、课程设计或电力信息化入门的完整参考。系统围绕智能电表数据采集、远程抄表、实时监控与用电分析展开涵盖前端界面、后端业务逻辑与数据库管理。压缩包共47个文件大小1.75MB包含16个class编译文件、15个java源文件、3个jar依赖库以及xml、classpath、project等工程配置另有docx格式的配置说明和数据库字典及流程文档txt错误日志、md说明与jpg流程图辅助阅读。目录按src/bin/lib/.settings等组织便于直接导入开发环境理解项目结构。除源码外还提供部署所需的服务器、数据库与应用程序配置指引以及电表数据采集、查询、分析的完整流程说明附数据库字典、异常日志与项目说明文档能帮助快速掌握从环境搭建到功能运行的关键环节。已有72人学习下载适合需要搭建同类系统或梳理Java后台开发流程的读者参考。1. 基于Java的智能电表采集系统毕设要交源码、配置说明和流程说明难点究竟在哪拿到「基于Java编写的智能电表采集系统源码配置说明流程说明」这个标题时第一反应别是去背八股文先想清楚现场是什么。这套系统说白了就是一台上位机通过RS485串口或者网口定时去问电表「你现在电压多少、电流多少、走了多少度电」电表回一帧十六进制报文程序拆开、校验、存库。所谓「智能」大部分工作其实是把电表那套二进制帧协议伺候明白Java本身反而常见。适合两类人一类是做Java课程设计、毕业设计需要一份能真跑、能讲清楚流程的可运行代码另一类是刚进公司被分到采集类项目的后端想在动手前知道坑在哪。本文按「选型 → 最小可运行实现 → 配置与流程 → 踩坑 → 加分项」的顺序把这条线捋完照着做就能交付一个能连电表、能解析DL/T645帧、能补采的版本。2. 先懂现场再写代码智能电表采集系统要解决的四件事2.1 规约先行DL/T645和Modbus RTU到底选哪个写采集系统第一件事不是建Spring Boot工程是确定电表那边的通信协议。国内民用电表、绝大多数工商业电表走的都是DL/T645国标帧格式是固定的一套帧头0x68、6字节地址域、第二帧头0x68、控制码、数据域长度、数据域、校验和、结束符0x16。2007版兼容1997版大部分电表出厂默认两者都支持只是数据标识略有差别。另一种常见协议是Modbus RTU。Modbus的好处是寄存器寻址直观请求里直接给寄存器地址和数量解析时按表映射就行很多电力采集模块、PLC场景默认它就是Modbus。但到了电表这边Modbus反而麻烦每个厂商把电压、电流、电能放在哪几个寄存器完全没有统一规范你得对着厂商手册一个一个映射。DL/T645虽然帧结构看起来绕但数据标识是全国统一的换表不换代码这是毕设答辩时最好讲的一点。我的建议是毕设主做DL/T645Modbus做成可选适配器。因为评阅老师大概率知道DL/T645你拿出「帧组装、校验、解析」这套完整链路比「我调了一个Modbus库」有说服力得多。真到了工业现场主流电表厂商比如威胜、科陆、许继都支持DL/T645做适配也不会白做。DL/T645里最容易被忽略的是字节序。地址域和电能数据都是低字节在前比如表号123456789ABC在帧里排成BC 9A 78 56 34 12不是正序排。数据域里的电量值4字节BCD码也是低字节在前。写过一次就知道这种反人类排列就是毕设里「源码能跑但数据全对不上」的头号来源。2.2 Java技术栈怎么选Netty、串口库和定时任务的取舍确定了协议再看Java侧怎么做。完整采集系统涉及四块串口通信、帧编解码、定时采集、数据落库。我的常规选型如下串口通信首选jSerialComm跨平台、Maven直接引、不用像Rxtx那样配本地dll或者so文件。毕设答辩时现场换台电脑最容易翻车的不是代码是Rxtx没装好导致的UnsatisfiedLinkError换jSerialComm能少掉一半泪。如果走网口采集那直接上Netty把协议解析器套进去但RS485是电表最普遍的物理层串口这条路绕不开。定时采集Spring自带的Scheduled够用但有个隐藏坑——默认单线程多个任务互相排队一个任务卡住后面全卡。更好的是在配置类里new一个ScheduledExecutorService指定核心线程数采集任务和补采任务分开跑。这样就算某块表一直超时也不影响整轮采集。数据落库简单项目用JdbcTemplate或者MyBatis-Plus都行。毕设规模不需要引入一堆中间件但有一件事要注意采集数据不能一条一条insert。电表采集是高频小数据一个站几十块表每30秒一轮逐条insert迟早把数据库拖垮。批量batchUpdate每批500条这是必须写的。这套选型下来整个工程依赖很少spring-boot-starter-web、jSerialComm、mybatis-plus或spring-boot-starter-jdbc、mysql-connector-java。不需要netty、不需要quartz、不需要redis保持轻量答辩时候你能把每一行依赖都讲出用途比堆一堆组件然后说不出为什么强。3. 把最小可运行系统搭出来源码结构和核心流程3.1 源码目录怎么摆从入口到采集线程的一条线毕设源码最怕两个极端一个类干所有事或者十几个Controller毫无分层。我的建议是四层config放串口配置、protocol放帧编解码、collector放采集调度、entity/mapper放数据落库。下面这个结构够用又不显得单薄meter-collector/ ├── pom.xml ├── src/main/java/com/example/meterec/ │ ├── MeterCollectorApplication.java # Spring Boot 入口 │ ├── config/ │ │ ├── SerialPortConfig.java # 串口参数初始化 │ │ └── ScheduleConfig.java # 采集线程池配置 │ ├── protocol/ │ │ ├── Dl645Frame.java # 帧实体地址/控制码/数据域 │ │ └── Dl645Coder.java # 帧组装、校验、解析 │ ├── collector/ │ │ └── MeterCollector.java # 定时采集调度 │ ├── entity/ │ │ └── MeterRecord.java # 一条采集记录 │ └── mapper/ │ └── MeterRecordMapper.java # 批量写库 └── src/main/resources/ ├── application.yml # 串口、采集周期、数据库配置 └── db/ └── schema.sql # 建表脚本注意协议包和采集包要分开。协议解析是纯算法不依赖Spring采集调度是业务编排依赖Spring管理生命周期。分开写的好处是你可以用main函数直接单测Dl645Coder不用启动整个Spring容器。我一般写协议类时不注入任何Spring注解保持纯POJO这样换协议实现时不影响上层。3.2 核心代码走读帧组装、发送、校验、解析一条链DL/T645最核心的是Dl645Coder这个类里面做三件事根据表号和命令组装读帧、发送后接收响应、解析响应帧为可读数据。先看组装部分// Dl645Coder.java —— 组装一条读数据帧 // 以读正向有功总电能为例子1997版数据标识是 90 10 00 00 public byte[] buildReadFrame(byte[] meterAddress, byte[] dataMark) { // 帧结构68 地址(6B) 68 控制码(01读) L(数据域长度) // 数据域 4字节数据标识长度L此时为 4 byte[] frame new byte[2 6 1 1 4 1 1]; int idx 0; frame[idx] 0x68; System.arraycopy(meterAddress, 0, frame, idx, 6); // 低字节在前的表号 idx 6; frame[idx] 0x68; frame[idx] 0x01; // 0x01 读0x04 写 frame[idx] 0x04; // 数据域长度读命令就是4 System.arraycopy(dataMark, 0, frame, idx, 4); idx 4; frame[idx] calcChecksum(frame, idx); // CS 校验从68到数据域末尾 frame[idx 1] 0x16; // 帧结束符 return frame; }这里有两个参数必须说明。meterAddress是表号按低字节在前排列后的6字节数组不是字符串。dataMark是数据标识比如读正向有功总电能是90 10 00 00读A相电压是B6 11 00 00具体看电表手册不同厂商可能略有差异务必以实表为准。CS校验是新手最容易写错的地方。算法是从第一个0x68开始到数据域最后一个字节为止所有字节累加取低8位。注意长度字节L也要参与累加很多实现把L漏了就永远校验失败。看一下校验代码// 计算CS校验 —— 累加从帧头到数据域末尾的所有字节取低8位 private byte calcChecksum(byte[] frame, int endExclusive) { int sum 0; for (int i 0; i endExclusive; i) { sum frame[i] 0xFF; // 必须与0xFF做与运算否则负数会把高位带进求和 } return (byte) (sum 0xFF); }与0xFF这一步很关键。Java的byte是有符号的0x68直接相加没问题但0x90在Java里是负的不转成0-255范围累加结果必然错。调试时发现CS死活不对先看是不是忘了 0xFF这是血泪经验。再说响应解析。电表返回的帧是68 地址 68 控制码(0x81表示读响应) L 数据域 CS 16。数据域前4字节是回显的数据标识后面跟着实际数据。比如电量数据是4字节BCD低字节在前。解析代码// 解析电量值4字节BCD低字节在前每个字节高4位是个位上的十位 // 示例0x56 0x34 0x12 0x00 - 1234.56 kWh按0.01度倍率 private long parseBcd4(byte[] data, int offset) { long value 0; for (int i 3; i 0; i--) { byte b data[offset i]; int hi (b 4) 0x0F; // 高4位 int lo b 0x0F; // 低4位 value value * 100 (hi * 10 lo); } return value; // 实际电量还需要除以倍率比如除以100 }这里从高位字节往低位字节组数因为数据是低字节在前所以倒序遍历。每个字节拆成两个BCD数字塞进十进制计数里。拿到原始值后再看电表铭牌上的倍率——多数表是0.01或0.1除以对应倍数才是真正的kWh。倍率写死在配置里不要硬编码否则换块表数据就差一个数量级。3.3 采集调度与批量写库别让串口读卡死整条链路采集线程要处理的不只是「定时发命令」还有各种超时和重试。我一般用ScheduledExecutorService采集间隔用scheduleWithFixedDelay而不是scheduleAtFixedRate。原因是fixedRate是固定起跑线上一个任务没跑完下一个到点就挤进来串口会乱fixedDelay是上一个跑完再等30秒天然避免并发抢占串口。// ScheduleConfig.java —— 采集线程池与写库线程分离 Bean(destroyMethod shutdown) public ScheduledExecutorService meterExecutor() { // 核心线程数 采集线程1 补采线程1多留一个给状态上报 return Executors.newScheduledThreadPool(3); } // MeterCollector.java —— 每轮采集的入口 public void collectOnce() { ListMeterRecord batch new ArrayList(500); for (String meterAddress : meterAddressList) { try { MeterRecord record readOne(meterAddress); // 组帧收帧解析 if (record ! null) { batch.add(record); } } catch (TimeoutException e) { continue; // 单块表超时不阻塞整轮 } } if (!batch.isEmpty()) { meterRecordMapper.batchInsert(batch); // 批量写库 } }readOne内部要加超时控制。jSerialComm的读取超时设置成1000ms读不到就返回然后continue进入下一块表。这里有一个设计取舍是「一轮全部完成再入库」还是「读一块存一块」。我建议攒成一批再入库。因为电表采集频率高写库次数少数据库压力小答辩时还能解释「批量插入」这个优化点一举两得。批量插入的SQL也要注意MyBatis的foreach拼接不要一次拼几千条MySQL默认max_allowed_packet会爆。每批控制在500条用ExecutorService线程池里的写库线程去执行不影响下一轮采集。至此最小系统已经能跑通启动Spring Boot打开串口定时去读存库。4. 配置说明和流程说明照着配就能连上电表4.1 配置项逐项拆解串口、表号、采集周期、数据库交付毕设时「配置说明」文档最重要代码讲不明白的地方配置全都能解释。我习惯把所有现场参数收口到application.yml不散落在代码里。一份典型配置如下meter: serial: port: COM3 # Windows串口号Linux/macOS 是 /dev/ttyUSB0 或 /dev/ttyS0 baudRate: 2400 # DL/T645 默认2400波特率也有1200/4800对照铭牌 dataBits: 8 # 数据位8位DL/T645标准就是8 stopBits: 1 # 停止位1位 parity: NONE # 校验位无 timeoutMs: 1000 # 单帧读取超时单位毫秒 metering: addressList: - 123456789ABC # 表号16进制字符串写成方便看的形式 - 123456789ABD readIntervalSec: 30 # 采集周期生产环境一般15到60秒 retryTimes: 2 # 单块表失败后的重试次数 multiplyRate: 0.01 # 电量倍率每块表可能不同最好像addressList一样配成列表 database: batchSize: 500 # 批量写库条数串口参数里最容易错的是校验位。DL/T645规范里是偶校验吗不同厂商不完全一样默认是NONE但威胜某些老表要求EVEN。这一项必须看现场电表铭牌或者调试软件里的实际连接参数不能凭感觉。采集周期设30秒是个合理默认。设太短比如5秒RS485总线上指令还没回完下一轮就发出去整个总线会吵起来表现为时好时坏。设太长比如10分钟又体现不出「实时采集」。毕设演示用30秒既能看到数据变化又不会在答辩现场翻车。地址列表和倍率使用要配套。采集常见指标是电压、电流、功率、电量四类光一个电量就得知道每块表的倍率。不同表倍率可能不一样所以我建议addressList直接换成对象列表地址倍率采集项。配成这样meter: metering: meters: - address: 123456789ABC multiplyRate: 0.01 dataMarks: [9010, B611] # 电量、A相电压 - address: 123456789ABD multiplyRate: 0.1 dataMarks: [9010]dataMarks对应DL/T645数据标识写成16进制字符串编译时转成字节数组。这样换现场表计只改配置文件代码一行不动答辩时演示「换表不改代码」非常加分。4.2 采集流程全链路从发命令帧到写库的状态机「流程说明」文档不能只画一张大箭头图要落到每个环节的异常处理。采集一次数据的完整流程是七步启动时初始化串口打开失败则记录错误并进入重试状态。对每块表先组装读帧把表号、数据标识塞进Dl645Frame。通过串口发送帧发送前清空输入缓冲避免上次残留数据干扰。读取响应按帧头0x68定位超时未收到直接判定失败。校验CS和结束符0x16校验不过丢弃整帧重新等待下一帧。解析数据域按数据标识区分是电量还是电压乘上倍率。攒够批量或一轮结束统一写库记录采集时间。每一步的失败都要有对应动作。发送失败重试2次再跳过解析失败丢弃这一帧但不采空写库失败打印日志但不影响下一轮采集。流程描述要能让人看出「系统不会因为一块坏表就整个停摆」这是毕业设计答辩时的高频问题大多数同学的版本里一块表超时后面全卡住这就是最明显的扣分点。我另外会在流程说明里加一张状态表把每一帧的状态变化写清楚。比如发送前状态是READY发送后进入WAIT_RESPONSE收到完整帧且校验通过后进入PARSE_OK超时进入RETRY。这样评审老师一眼就能看出你考虑到了异常处理不是只能跑通happy path。关于串口读取还有个细节要写进流程每次发送前先清空串口输入缓冲。因为上一轮如果有残余字节没读完这一轮会误当新帧解析结果校验失败。jSerialComm里用flushInput方法发送前调一次能显著降低「时好时坏」的玄学问题。5. 避开这五个坑串口乱码、粘包断帧、校验算错、采集周期、调度并发5.1 现象读到的十六进制全是乱码或负数这是接触串口编程第三天最容易遇到的事。明明电表回了一大串字节打印出来乱七八糟或者用String转了一下全变问号。原因有两个第一串口库读取时把字节流转成了字符串涉及编码转换0x90这种高位字节被转码成Unicode就废了第二Java的byte有符号直接打印会看到一堆负数。解决方式很死板但有效全程只用byte数组任何环节不做String转换打印用Hex工具类解析按位与0xFF。我一般会在Dl645Coder里写一个toHex方法调试时逐字节打印真实值。另外读取字节时不要用readLine那类方法那是给文本协议用的要直接read(byte[], off, len)。5.2 现象连续采集几轮后解析帧头错位、数据对不上典型情况是跑了两三分钟正常突然第一块表的电量变成了几百亿再往后全是校验失败。这个叫粘包和断帧。串口是流式的上一帧读慢了下一帧已经从电表发出两个帧的头尾粘在一起或者读取超时设得太短一个帧被拆成两半读到。解决方式是分帧器不能每轮new一个buffer直接读固定长度。我采用的做法是维护一个全局的ByteArrayOutputStream每次读到的原始字节先append进去然后反复在缓冲里找0x68开头、完整长度满足、结尾是0x16的帧。找到就切出去找不到就等下一批字节进来。这个思路跟Netty的ByteToMessageDecoder是一回事毕设里写出这个粘包处理是个很好的加分点。5.3 现象CS校验永远不对但报文看着没问题这个坑我在3.2节提过一半还有另一半是表号排列。报文格式完全正确但地址域按「正序」填进去了导致CS校验时电表返回的帧你本地算出来的对不上。地址域必须低字节在前校验计算时把第一个0x68到数据域末尾全算进去漏掉长度字节也是高频翻车原因。解决方式是写一段独立单测构造一个已知帧手算CS然后断言Dl645Coder算出来结果一致。在纯Java环境里跑main函数就能验证不用连电表。这个测试用例本身就是「源码配置说明」之外第三份文档的最好素材把期望报文和实际报文都贴进去答辩时一摆比空口解释强得多。5.4 现象采集周期设短之后一块表偶尔读不到重试还加重RS485是半双工总线所有表共用一对线。你5秒发一次命令上一轮的响应还没走完下一轮命令又开场了总线冲突。表多的时候串口缓冲区里全是残帧CS校验几乎全挂但单块表单独Debug时又是好的。解决方式是两件事一是采集周期不能小于「单块表最大响应时间×表数」一般电表响应在200毫秒内30秒周期绰绰有余二是重试要退避第一次失败等1秒再重试第二次等2秒不要立即重试否则越重试总线越乱。把退避参数写进配置说明里讲清楚「为什么不是越勤越好」这是体现工程素养的地方。5.5 现象用了Scheduled后写库越来越慢最后采集任务被堵死Scheduled默认单线程采集任务和写库任务在同一个线程池里排队。采集一轮要串口等待写库要数据库IO这两个任务互相等最终表现为「界面数据几分钟刷新一次」。多块表时更明显一块表超时1秒后面所有表都跟着等。解决方式是拆线程池。采集线程池两个线程够了一个负责正常轮询一个负责重试写库单独一条线程。线程池不要用无界队列采集是连续任务无界队列攒多了内存涨。核心线程数不用大3到4个足够。把这个设计写进配置说明的「线程模型」小节比堆代码更省答辩时间。6. 让毕业设计多拿分加个模拟器验证和断点续采系统能跑通只是合格想往上走一步加两件事协议模拟器和补采机制。没有真实电表或现场不方便连表时模拟器是验证你的帧编解码对不对的唯一手段。做法很简单用jSerialComm开一对虚拟串口或者直接在本机监听一个ServerSocket模拟器收到请求帧后按DL/T645格式拼一条响应帧原路返回。这样你的协议层和采集层可以脱离硬件完全自测。模拟器实现里最重要的就是按「真实时序」回包不能秒回。真实电表收到命令后要几十毫秒处理模拟器加个100毫秒延迟这样超时逻辑才会被真实测试到。很多毕设答辩现场没带电表老师的第一个问题往往是「你这系统接不到表怎么演示」有模拟器当场演示一套完整流程这个细节比任何截图都好使。补采机制则是生产环境里刚需的功能某块表网络闪断、串口被拔这一轮没采到系统得记下来等下一轮空闲时按时间顺序补上。实现思路是在表对象里加一个lastSuccessTime字段采集失败且当前时间与上次成功时间之差超过N个周期就触发一次补采补采成功就重置时间。控制逻辑不复杂但体现了对「数据完整性」的考虑毕业设计里这是拉开档次的点。做完这两件事再把采集数据接一个简单可视化页面用ECharts画电压和电量的趋势线整个课题的闭环就完整了。我的习惯是代码能跑之前先不写配置说明而是先把「每一条配置项对应的线上现象」记在草稿里比如波特率错了是什么表现、校验位错了是什么表现最后再整理进文档这样写出来的配置说明每一行都有出处不会变成教材搬运。这套系统做完给我最大的教训是采集类项目真正的复杂度不在Java语法而在协议和时序上。多花一小时写模拟器、写粘包测试能省下三天在串口调试助手里肉眼对字节。希望这个Java实现的智能电表采集系统方案能帮你在动手前避开那些绕远路的坑。本文还有配套的精品资源点击获取
返回列表