电表通讯协议DLT645-2007编程

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);
        }
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity坦克大战开发全流程——游戏场景——主玩家——可击毁箱子

游戏场景——主玩家——可击毁箱子 添加特效 CubeObj的代码如下 using System.Collections; using System.Collections.Generic; using UnityEngine;public class CubeObj : MonoBehaviour {//关联的奖励物品public GameObject[] rewardObjects;//关联的特效public GameObject …

【分布式配置中心】聊聊Apollo的安装与具体配置变更的原理

【管理设计篇】聊聊分布式配置中心 之前就写过一篇文章&#xff0c;介绍配置中心&#xff0c;但是也只是简单描述了下配置中心的设计点。本篇从apollo的安装到部署架构到核心原理进一步解读&#xff0c;大概看了下apollo的原理&#xff0c;感觉没有必要深究&#xff0c;所以就…

RHCE9学习指南 第12章 ssh远程登录系统和远程拷贝

很多时候服务器并没有显示器&#xff0c;我们也不可能每次都通过控制台去管理服务器&#xff0c;这时就需要远程登录。远程登录到服务器可以通过Telnet或ssh的方式。但是用Telnet登录&#xff0c;整个过程都是以明文的方式传输的&#xff0c;不安全。所以&#xff0c;建议使用s…

Cisco模拟器-交换机端口的隔离

设计要求将某台交换机的端口划分在不同的VLAN。以实现连接在相同VLAN端口上的计算机可以通信&#xff0c;而连接在不同VLAN端口上的计算机无法通信的目的。 通过设计&#xff0c;一方面可以加强计算机网络的安全&#xff0c;另一方面通过隔绝不同VLAN间的广播包也可以提高网络…

Springboot整合MybatisPlus的基本CRUD

目录 前言1. 搭建项目2. 基本的CRUD 前言 发现项目框架是MybatisPlus的&#xff0c;由于个人使用该框架的CRUD比较少 对此学习过程中&#xff0c;从零到有开始搭建学习还是比较重要的&#xff0c;感悟会比较多 关于各个类的使用&#xff0c;可看如下文章&#xff1a; 剖析Ja…

DES加密算法优缺点大揭秘:为何它逐渐被取代?

一、引言 DES&#xff08;Data Encryption Standard&#xff09;加密算法作为一种历史悠久的对称加密算法&#xff0c;自1972年由美国国家标准局&#xff08;NBS&#xff09;发布以来&#xff0c;广泛应用于各种数据安全场景。本文将从算法原理、优缺点及替代方案等方面&#…

C. Load Balancing 一个序列同时加一个数和减一个数,直到最大和最小之间相差最大为1(结论可记住)

题目&#xff1a; https://atcoder.jp/contests/abc313/tasks/abc313_c 思想&#xff1a;1.给定一个固定的B&#xff0c;求使A等于B所需的最小运算次数 2.在所有最大值和最小值最多相差1的B中&#xff0c;找出一个所需的运算次数最少的&#xff0c;即1 做法&#xff1a;构造…

Unity中裁剪空间推导(使用FOV来调节)

文章目录 前言一、使用FOV代替之前使用的Size&#xff08;h&#xff09;1、我们可以把矩阵中使用到 h(高) 和 w(宽) 的部分使用比值替换掉。2、替换后 前言 在之前的文章中&#xff0c;我们控制透视相机使用的是SIze。但是&#xff0c;在透视相机中&#xff0c;我们使用的是FO…

Python编程技巧 – format格式化文本

Python编程技巧 – format格式化文本 Python Programming Essentials - Using format() to format texts By JacksonML 本文简要介绍Python语言的format()方法&#xff08;也即函数&#xff09;相关实例和技巧&#xff0c;希望对读者有所帮助。 1. format定义和方法 forma…

修改源码,element的el-table合并,处理合并产生的hover样式问题

1、确认自己element-ui的版本号 2、此element-ui下的lib包是修改过hover样式的包,如何替换自己文件下的node_modules中的包 修改后将lib文件夹中文件替换你项目中/node_module/element-ui/Lib中的文件问题??如果替换开发环境中的node_module的包无法升级到测试环境,因为nod…

软件测试/测试开发丨Python 内置库 正则表达式re

什么是正则表达式 正则表达式就是记录文本规则的代码可以查找操作符合某些复杂规则的字符串 使用场景 处理字符串处理日志 在 python 中使用正则表达式 把正则表达式作为模式字符串正则表达式可以使用原生字符串来表示原生字符串需要在字符串前方加上 rstring # 匹配字符…

操作系统大题

目录 作业一&#xff1a; 前驱图 作业二&#xff1a;信号量 作业三&#xff1a;同步算法 1‘’生产者消费者问题 解1&#xff1a; 解2&#xff1a;利用AND信号量解决生产者-消费者问题 解3. 利用管程解决生产者-消费者问题 2‘’ 哲学家进餐问题&#xff08;The Dinning…

数据结构之树 --- 二叉树 < 堆 >

目录 1. 树是什么&#xff1f; 1.1 树的表示 2. 二叉树 2.1 二叉树的概念 2.2 特殊的二叉树 2.3 二叉树的性质 2.4 二叉树的存储结构 2.4.1 顺序存储 2.4.2 链式存储 3. 二叉树顺序结构的实现 <堆> 3.1 二叉树的顺序结构 ​编辑 3.2 堆的概念及结构 ​编辑…

C#的checked关键字判断是否溢出

目录 一、定义 二、示例&#xff1a; 三、生成&#xff1a; 一、定义 使用checked关键字处理溢出。 在进行数学运算时&#xff0c;由于变量类型不同&#xff0c;数值的值域也有所不同。如果变量中的数值超出了变量的值域&#xff0c;则会出现溢出情况&#xff0c;出现溢出…

自然语言处理3——玩转文本分类 - Python NLP高级应用

目录 写在开头1. 文本分类的背后原理和应用场景1.1 文本分类的原理1.2 文本分类的应用场景 2. 使用机器学习模型进行文本分类&#xff08;朴素贝叶斯、支持向量机等&#xff09;2.1 朴素贝叶斯2.1.1 基本原理2.1.2 数学公式2.1.3 一般步骤2.1.4 简单python代码实现 2.2 支持向量…

【惊喜揭秘】xilinx 7系列FPGA时钟区域内部结构大揭秘,让你轻松掌握!

本文对xilinx 7系列FPGA的时钟路由资源进行讲解&#xff0c;内容是对ug472手册的解读和总结&#xff0c;需要该手册的可以直接在xilinx官网获取&#xff0c;或者在公众号回复“xilinx手册”即可获取。 1、概括 7系列器件根据芯片大小不同&#xff0c;会有8至24个时钟区域&…

在Pyqt5的QtWidgets.QGraphicsView上绑定matplotlib.figure实现绘图

matplotlib的基础类figure相当于一个View窗口类&#xff08;实际上&#xff0c;每一个figure是由更底层canvas来控制的&#xff0c;大概有点类似CAD的layers层的概念&#xff09;&#xff0c;是一个可绘制显示图形的View区域&#xff0c;也称画布&#xff08;figure&#xff09…

OSG绘制视锥体(升级版)

OSG绘制视锥体&#xff0c;这一篇增加设置相机参数接口&#xff0c;支持通过eye、center、up设置相机参数。 代码如下&#xff1a; #include "stdafx.h" #include <osgViewer/Viewer> #include <osg/ShapeDrawable> #include <osg/Geode> #includ…

阿里开源大模型 Qwen-72B 私有化部署

近期大家都知道阿里推出了自己的开源的大模型千问72B&#xff0c;据说对于中文非常友好&#xff0c;在开源模型里面&#xff0c;可谓是名列前茅。 千问拥有有强大的基础语言模型&#xff0c;已经针对多达 3 万亿个 token 的多语言数据进行了稳定的预训练&#xff0c;覆盖领域、…

最新Redis7哨兵模式(保姆级教学)

一定一定要把云服务器的防火墙打开一定要&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;否则不成功&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&…