电表通讯协议DLT645-2007编程

news2025/4/22 8:28:56

1、协议

电表有个电力行业推荐标准《DLT645-2007多功能电能表通信协议》,电表都支持,通过该协议读取数据,不同的电表不需要考虑编码格式、数据地址、高低位转换等复杂情况,统一采集。

不方便的地方在于这个协议定义得有点小复杂,自己带有各种特殊性定义,编程时一堆的坑。

不少电表可以同时支持DLT645-2007和MODBUS RTU协议,但MODBUS协议在不同的电表中,地址都是不同的,需要查阅手册才能搞定。

DLT645不同的数据需要发送独立的请求,而Modbus数据地址连接的可以一次读取,各有所长和优势。

2、协议定义和报文

      协议定义和报文,在协议文档中有详细说明,主要部分节选如下:

3、数据域定义

         协议中对每个数据都定义了明确的地址和解析格式,如:

比如 00 00 00 00表示组合有功总电能。同一组的可以使用FF通配,表示读取整块的数据,00 00 FF 00表示一次读取 00 00 00 00 ~ 00 00 3F 00的所有数据。

 但需要注意的事,不是所有电表都支持块数据读取。

4、编码验证

    public class DLT645_2007
    {
        public enum CommandType
        {
            ReadData = 0x11,
            WriteData = 0x14,
            ReadAddress = 0x13,
            WriteAddress = 0x15
        }
        public class Dlt645Addr
        {
            public string dataIndentifier;
            public int dataLen;
            public float divisor = 1.0f;
            public string unit;
            public string dataItemName;
            public string dataName;
            public string PascalType;
            public Dlt645Addr(string dataIndentifier, int dataLen, float divisor, string unit, string dataItemName, string dataName= "", string PascalType = "FLOAT")
            {
                this.dataIndentifier = dataIndentifier;
                this.dataLen = dataLen;
                this.divisor = divisor;
                this.unit = unit;
                this.dataItemName = dataItemName;
                this.dataName = dataName;
                this.PascalType = PascalType;
            }
        }
        public Dlt645Addr []addrList =
        {
            //电能量
            new Dlt645Addr("00010000",4,100,"V" ,"正向有功总电量","Forth_Have_Power_Total"),// R4
            new Dlt645Addr("00010100",4,100,"V" ,"正向有功尖电量","Forth_Have_Power_SPIKE"),// R4 以上四个名称待核实
            new Dlt645Addr("00010200",4,100,"V" ,"正向有功峰电量","Forth_Have_Power_PEAK"),// R4
            new Dlt645Addr("00010300",4,100,"V" ,"正向有功平电量","Forth_Have_Power_FLAT"),// R4
            new Dlt645Addr("00010400",4,100,"V" ,"正向有功谷电量","Forth_Have_Power_VALLEY"),// R4

            //变量数据
            new Dlt645Addr("02010100",2,  10,"V","A项电压","Phase_A_Volt"),// R2
            new Dlt645Addr("02010200",2,  10,"V","B项电压","Phase_B_Volt"),// R2
            new Dlt645Addr("02010300",2,  10,"V","C项电压","Phase_C_Volt"),// R2

            new Dlt645Addr("02020100",3,1000,"A","A项电流","Phase_A_Elec"),// R3
            new Dlt645Addr("02020200",3,1000,"A","B项电流","Phase_B_Elec"),// R3
            new Dlt645Addr("02020300",3,1000,"A","C项电流","Phase_C_Elec"),// R3
            
            new Dlt645Addr("02030000",3,1000,"kW","瞬时总有功率功率","Instant_Have_Power_Rate_Total"),// R3
            new Dlt645Addr("02030000",3,1000,"kW","瞬时A相有功率功率","Instant_Phase_A_Have_Power_Rate"),// R3
            new Dlt645Addr("02030000",3,1000,"kW","瞬时B相有功率功率","Instant_Phase_B_Have_Power_Rate"),// R3
            new Dlt645Addr("02030000",3,1000,"kW","瞬时C相有功率功率","Instant_Phase_C_Have_Power_Rate"),// R3

            new Dlt645Addr("02040000",3,1000,"kwar","瞬时总无功率功率","Instant_None_Power_Rate_Total"),// R3
            new Dlt645Addr("02040000",3,1000,"kwar","瞬时A相无功率功率","Instant_Phase_A_None_Power_Rate"),// R3
            new Dlt645Addr("02040000",3,1000,"kwar","瞬时B相无功率功率","Instant_Phase_B_None_Power_Rate"),// R3
            new Dlt645Addr("02040000",3,1000,"kwar","瞬时C相无功率功率","Instant_Phase_C_None_Power_Rate"),// R3

            new Dlt645Addr("02050000",3,1000,"kVA","瞬时总视在功率","Instant_Apparent_Power_Rate_Total"),// R3
            new Dlt645Addr("02050000",3,1000,"kVA","瞬时A相视在功率","Instant_Phase_A_Apparent_Power_Rate"),// R3
            new Dlt645Addr("02050000",3,1000,"kVA","瞬时B相视在功率","Instant_Phase_B_Apparent_Power_Rate"),// R3
            new Dlt645Addr("02050000",3,1000,"kVA","瞬时C相视在功率","Instant_Phase_C_Apparent_Power_Rate"),// R3

            new Dlt645Addr("02060000",2,1000,"","总功率因数","Power_Rate_Factor_Total"),// R2
            new Dlt645Addr("02060100",2,1000,"","A相功率因数","Phase_A_Power_Rate_Factor"),// R2
            new Dlt645Addr("02060200",2,1000,"","B相功率因数","Phase_B_Power_Rate_Factor"),// R2
            new Dlt645Addr("02060300",2,1000,"","C相功率因数","Phase_C_Power_Rate_Factor"),// R2
            
            new Dlt645Addr("02070100",2,  10,"V","A相相角","Phase_A_Angle"),// R2
            new Dlt645Addr("02070200",2,  10,"V","B相相角","Phase_B_Angle"),// R2
            new Dlt645Addr("02070300",2,  10,"V","C相相角","Phase_C_Angle"),// R2

            new Dlt645Addr("02080100",2, 100,"%","A相电压波形失真度","Phase_A_Volt_Waveform_Distortion"),// R2
            new Dlt645Addr("02080200",2, 100,"%","B相电压波形失真度","Phase_B_Volt_Waveform_Distortion"),// R2
            new Dlt645Addr("02080300",2, 100,"%","C相电压波形失真度","Phase_C_Volt_Waveform_Distortion"),// R2

            new Dlt645Addr("02090100",2, 100,"%","A相电流波形失真度","Phase_A_Elec_Waveform_Distortion"),// R2
            new Dlt645Addr("02090200",2, 100,"%","B相电流波形失真度","Phase_B_Elec_Waveform_Distortion"),// R2
            new Dlt645Addr("02090300",2, 100,"%","C相电流波形失真度","Phase_C_Elec_Waveform_Distortion")// R2

            //最大需量

            //事件记录数

            //负荷记录

            //参变量

            //安全认证

        };
        Dictionary<string,Dlt645Addr> dictAddr = new Dictionary<string, Dlt645Addr> ();

        //是否需要唤醒 ,0xFE
        private bool isNeedWakeUp = false;

        // 创建串口对象
        SerialPort serialPort = null;

        int rxCount = 0;
        byte[] rxdata = new byte[512];

        Stopwatch swRead = new Stopwatch ();

        public DLT645_2007()
        {
            foreach(var addr in addrList)
            {
                dictAddr[addr.dataIndentifier] = addr; 
            }
        }

        //SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
        public bool OpenDevice(string portName, int baudRate= 2400, Parity parity = Parity.None, int dataBits=8, StopBits stopBits = StopBits.One)
        {
            try
            {
                serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
                serialPort.Open();
                serialPort.ReadTimeout = 500;
                serialPort.DataReceived += SerialPort_DataReceived;
                return true;
            }catch(Exception ex)
            {
                return false;
            }
        }

        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            byte[] rxTemp = new byte[1024];
            int rdCount = serialPort.Read(rxTemp, 0, rxTemp.Length);
            if (rdCount > 0)
            {
                for(int  i = 0; i < rdCount; i++)
                {
                    if (rxTemp[i] == 0xfe)
                        continue;
                    rxdata[rxCount++] = rxTemp[i];
                    if (rxTemp[i] == 0x16)
                    {
                        ParseData();
                    }
                }
            }
            swRead.Stop();
        }
        public void ParseDataTest(string data)
        {
            data = data.Replace(" ", "");
            rxCount = data.Length;
            for(int i = 0; i < rxCount;i+=2)
            {
                rxdata[i/2] = Convert.ToByte(data.Substring(i, 2), 16);
            }
            ParseData();
        }
        private void ParseData()
        {

            //校验和标识检查
            if(rxCount < 9 || rxdata[0] != 0x68 || rxdata[7] != 0x68)
            {
                Console.WriteLine($"校验和标识检查失败,len:{rxCount},{rxdata[0]:X},{rxdata[7]:X}");
                rxCount = 0;
                return;
            }
            //响应命令检查
            if (rxdata[8] != 0x91 && rxdata[8] != 0xB1 )
            {
                Console.WriteLine($"响应命令检查失败,{rxdata[8]:X}");
                rxCount = 0;
                return;
            }
            string addrT = BitConverter.ToString(rxdata, 1, 6).Replace("-","");
            string addr = addrT.Substring(8,2)+ addrT.Substring(6, 2) + addrT.Substring(4, 2) + addrT.Substring(2, 2) + addrT.Substring(0, 2);
            string value = "";
            int dataLen = rxdata[9];
            for(int k = 0; k < dataLen; k++)
            {
                rxdata[10 + k] -= 0x33;
                value += string.Format("{0:X2}", rxdata[10 + k]);
            }
            string index = value.Substring(6, 2)+ value.Substring(4, 2)+ value.Substring(2, 2)+ value.Substring(0, 2);
            var dlt645 = dictAddr[index];
            string value2 = "";
            for(int k = dlt645.dataLen-1; k >= 0; k --)
            {
                value2 += value.Substring(8 + k * 2, 2);
            }
            float fv = float.Parse(value2); 

            Console.WriteLine($"表号:{addr},{dlt645.dataItemName},{dlt645.dataIndentifier},{fv/dlt645.divisor} [{dlt645.unit}]");
            rxCount = 0;
        }

        public void CloseDevice()
        {
            serialPort.Close();
            serialPort = null;
        }

        public void Read(List<string> addrMeter, string data)
        {
            if (isNeedWakeUp)
            {
                byte[] reqHe = { 0xFE, 0xFE };
                serialPort.Write(reqHe, 0, reqHe.Length);
            }
            byte []req = BuildRequest(addrMeter[0], data, CommandType.ReadData);
            serialPort.Write(req,0,req.Length);
            swRead.Restart();
        }
        public byte[] BuildRequest(string addrMeter, string data, CommandType commandType)
        {
            // 构建请求帧
            byte[] request = new byte[12 + 4];
            int index = 0;
            request[index++] = 0x68;  // 帧起始符 68H
            //地址域 A0~A5,地址域是用来表示电表地址,低位在前,高位在后
            request[index++] = Convert.ToByte(addrMeter.Substring(10, 2), 16);  // 地址域 A0
            request[index++] = Convert.ToByte(addrMeter.Substring(8, 2), 16);  // 地址域 A1
            request[index++] = Convert.ToByte(addrMeter.Substring(6, 2), 16);  // 地址域 A2
            request[index++] = Convert.ToByte(addrMeter.Substring(4, 2), 16);  // 地址域 A3
            request[index++] = Convert.ToByte(addrMeter.Substring(2, 2), 16);  // 地址域 A4
            request[index++] = Convert.ToByte(addrMeter.Substring(0, 2), 16);  // 地址域 A5
            request[index++] = 0x68;  // 帧起始符
            request[index++] = 0x11;  // 控制码
            request[index++] = (byte)(4);// Convert.ToByte((data.Count * 4).ToString(), 16);  // 数据域长度
            for (int i = 3;i >=0; i --)
            {
                string sV = data.Substring(i*2, 2);
                if (sV.Equals("FF"))
                {
                    request[index++] = 0xff;
                }
                else
                {
                    request[index++] = (byte)(Convert.ToByte(sV, 16) + 0x33);  // 数据
                }
            }

            byte crc = GetCRC(request, 0, request.Length-2);  // 校验码

            request[index++] = Convert.ToByte(crc.ToString("X"), 16);
            request[index++] = 0x16;  // 结束符
            return request;
        }
        private byte GetCRC(byte[] data, int start, int length)
        {
            int I = 0;
            for (int k = 0; k < length; k++)
            {
                I += data[start + k];

            }
            return (byte)(I % 256);
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1346186.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构与算法】字符串匹配(头歌习题)【合集】

目录 第1关&#xff1a;实现朴素的字符串匹配任务描述相关知识编程要求评测说明完整代码 第2关&#xff1a;实现KMP字符串匹配任务描述相关知识编程要求评测说明完整代码 第3关&#xff1a;【模板】KMP算法任务描述相关知识C STL容器string1、string的定义2、string中内容的访问…

计算机网络-动态路由

网络层协议&#xff1a;ip&#xff0c;ospf&#xff0c;rip&#xff0c;icmp共同组成网络层体系 ospf用于自治系统内部。 一个路由器或者网关需要能够支持多个不同的路由协议&#xff0c;以适应不同的网络环境。特别是在连接不同自治系统的边缘路由器或边界网关的情况下&#…

第2课 使用FFmpeg读取rtmp流并用openCV显示视频

本课对应源文件下载链接&#xff1a; https://download.csdn.net/download/XiBuQiuChong/88680079 这节课我们开始利用ffmpeg和opencv来实现一个rtmp播放器。播放器的最基本功能其实就两个:显示画面和播放声音。在实现这两个功能前&#xff0c;我们需要先用ffmpeg连接到rtmp服…

蓝桥杯嵌入式KEY

1.按键原理图 2.按键GPIO引脚设置成输入&#xff0c;上拉模式 3.设置TIM4时钟源为外部时钟源 PSC为80-1 Period为10000-1 打开NVIC 中断时间为10ms 4.在bsp文件中添加interrupt.c文件 5.按键单击代码 6.长按键 7.按键过程和显示过程

动态规划 典型例题

总结 动态规划的的四个解题步骤是&#xff1a; 定义子问题写出子问题的递推关系确定 DP 数组的计算顺序空间优化&#xff08;可选&#xff09; from functools import cache cache #缓存&#xff0c;避免重复运算 def dfs(i)->int:if 终止: return 0 #具体返回什么值要看…

在线尺码计算

在线衣服尺码计算 尺码不确定的话&#xff0c;可以填写身高、体重生成可以参考的尺码还是不错的 工具简介 选购时请综合参考尺码表中的各项参数&#xff0c;这有助您选择到更好的尺码。 该尺码计算工具仅供参考&#xff0c;测量脚时请注意用适当力度轻踩水平面上。因测量方法不…

Tomcat和Servlet

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Tomcat是什么&#xff1f;1.1下载&#xff1a;1.2 tomcat是什么1.3启动服务器&#xff1a; 二.部署三、Servlet3.1创建项目3.2引入依赖pom.xml的所有代码 3…

electron——查看electron的版本(代码片段)

electron——查看electron的版本(代码片段)1.使用命令行&#xff1a; npm ls electron 操作如下&#xff1a; 2.在软件内使用代码&#xff0c;如下&#xff1a; console.log(process) console.log(process.versions.electron) process 里包含很多信息&#xff1a; process详…

Python装饰器的专业解释

装饰器&#xff0c;其实是用到了闭包的原理来进行操作的。 单个装饰器&#xff1a; 以下是一个简单的例子&#xff1a; def outer(func):print("OUTER enter ...")def wrapper(*args, **kwargs):print("调用之前......")result func(*args, **kwargs)p…

4.31 构建onnx结构模型-Tile

前言 构建onnx方式通常有两种&#xff1a; 1、通过代码转换成onnx结构&#xff0c;比如pytorch —> onnx 2、通过onnx 自定义结点&#xff0c;图&#xff0c;生成onnx结构 本文主要是简单学习和使用两种不同onnx结构&#xff0c; 下面以 Tile 结点进行分析 方式 方法一…

用Xshell连接虚拟机的Ubuntu20.04系统记录。虚拟机Ubuntu无法上网。本机能ping通虚拟机,反之不能。互ping不通

先别急着操作&#xff0c;看完再试。 如果是&#xff1a;本机能ping通虚拟机&#xff0c;反之不能。慢慢看到第8条。 如果是&#xff1a;虚拟机不能上网&#xff08;互ping不通&#xff09;&#xff0c;往下一直看。 系统是刚装的&#xff0c;安装步骤&#xff1a;VMware虚拟机…

DevOps系列 之 Python与Java互相调用的案例

Python和Java是两种非常流行的编程语言。Python是一种解释型语言&#xff0c;而Java则是一种编译型语言。两者都有广泛的应用&#xff0c;尤其是在测试领域。在本文中&#xff0c;我们将讨论如何使用Python测试Java源代码&#xff0c;Java如何调用Python脚本。 单元测试 单元…

redis的基本使用

一、 Redis简介 Redis是一个基于内存的 key-value 结构数据库。Redis是一款采用key-value数据存储格式的内存级NoSQL数据库&#xff0c;重点关注数据存储格式&#xff0c;是key-value格式&#xff0c;也就是键值对的存储形式。与MySQL数据库不同&#xff0c;MySQL数据库有表、…

WEB 3D技术 three.js 雾 基础使用讲解

本文 我们说一下 雾 在three.js中有一个 Fog类 它可以创建线性雾的一个效果 她就是模仿现实世界中 雾的一个效果 你看到远处物体会组件模糊 直到完全被雾掩盖 在 three.js 中 有两种雾的形式 一种是线性的 一种是指数的 个人觉得 线性的会看着自然一些 他是 从相机位置开始 雾…

Unity坦克大战开发全流程——开始场景——音效数据逻辑

开始场景——音效数据逻辑 从这里开始到后面的三小节我们都将干一件很重要的事——数据存储&#xff0c;只有实现了数据存储才能在再次进入游戏时保持游戏数据不被丢失。 类图分析&#xff1a;数据管理类是一个大类&#xff0c;它其中关联了两个类&#xff08;这两个类都是数据…

2023年终总结 —— 我和CSDN相遇的第一年之“技术学习和个人成长的回顾与展望”

​ ​ &#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 &#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 &#x1f38a;对2023的总结与回顾&#x1f38a; &#x1f3c5;获奖记录 &#x1f4da;学…

test mock-03-wiremock 模拟 HTTP 服务的开源工具 flexible and open source API mocking

拓展阅读 test 之 jmockit-01-overview jmockit-01-test 之 jmockit 入门使用案例 mockito-01-overview mockito 简介及入门使用 PowerMock Mock Server ChaosBlade-01-测试混沌工程平台整体介绍 jvm-sandbox 入门简介 wiremock WireMock是一个流行的开源工具&#xf…

蚂蚁实习一面面经

蚂蚁实习一面面经 希望可以帮助到大家 tcp建立连接为什么要三次握手&#xff1f; 三次握手的过程 注意&#xff1a;三次握手的最主要目的是保证连接是双工的&#xff0c;可靠更多的是通过重传机制来保证的 所谓三次握手&#xff0c;即建立TCP连接&#xff0c;需要客户端和…

Centos7部署Keepalived+lvs服务

IP规划&#xff1a; 服务器IP地址主服务器20.0.0.22/24从服务器20.0.0.24/24Web-120.0.0.26/24Web-220.0.0.27/24 一、主服务器安装部署keepalivedlvs服务 1、调整/proc响应参数 关闭Linux内核的重定向参数&#xff0c;因为LVS负载服务器和两个页面服务器需要共用一个VIP地…

力扣刷题记录(23)LeetCode:718、1143、1035

718. 最长重复子数组 要想到用一个二维数组dp去表示数组nums1和nums2的公共子数组的最大长度。其中二维数组的索引 i、j 分别表示nums1中[0,i-1]数组、nums2中[0,j-1]数组。如果满足nums1[i-1]nums2[j-1],那么dp[i][j]dp[i-1][j-1]1 class Solution { public:int findLength(v…