首页 > 其他分享 >DLT645-2007 协议快速入门

DLT645-2007 协议快速入门

时间:2024-10-15 10:59:26浏览次数:6  
标签:11 入门 bytes DLT645 System 2007 println byte 数据

@

目录

DLT645-2007 协议快速入门

1. 什么是DLT645-2007 协议

DLT645目前主要使用的有两个版本,DLT645-1997 和 DLT645-2007。DLT645协议是一种 问答式(主从式)通信规约 。在这种通信模式下,通常存在一个主站和一个或多个从站。主站负责发起通信请求,从站则根据主站的请求提供相应的响应。

问答式规约的通信过程通常包括以下几个步骤:

  • 主站发起请求:主站发送一个请求报文给特定的从站,请求报文中包含了需要从站执行的操作,如读取点能量、读实时参数等
  • 从站接收请求:从站接收到主站的请求后,根据请求的内容进行处理。
  • 从站准备响应:从站根据请求的内容准备相应的数据,并构造一个响应报文。
  • 从站发送响应:从站将构造好的响应报文发送回主站。
  • 主站接收响应:主站接收到从站的响应后,根据响应内容进行响应处理。

DLT645-2007 是中国电力行业标准,全称为《多功能电能表通信协议》,主要用于电力系统中电能表的通信。这个标准定义了电能表与数据终端设备之间进行数据交换时的物理层、链路层以及应用层的通信协议。DLT645-2007协议采用主-从结构的半双工通信模式,硬件接口通常使用 RS-485.

2. 帧格式

DLT645-2007协议为主-从结构的半双工通信模式。手持单元或其他数据终端为主站,多功能电能表为从站。每个多功能电能表均有各自的地址编码通信链路的建立与接触均由主站发出的信息帧来控制。每帧由起始符、从站地址域、控制码、数据域长度、数据域、帧信息纵向校验码以及帧结束符7个域组成。每部分由若干十六进制码组成。

image-20241015082840566

2.1 帧起始符

DLT645协议的数据帧每帧的开始都固定为 0x68,作为数据的起始符方便接收方做数据解析。

2.2 地址域

地址域为上图的 A0-A5,由6个字节构成。地址域是用来标识电表地址,低位在前,高位在后;在485总线上可能挂多个645设备,要找到指定的设备,必须要根据设备的地址查找。每台设备出厂会有自己的地址,也可以修改设备的通信地址。如下图,设备地址就是 220514030093,在传输时由于低位在前,高位在后,所以实际传输时地址为 930003140522.

1728952772456

需注意:

  • 通信地址 999999999999H 为广播地址,只针对特殊命令有效,如广播校时、广播冻结等。广播命令不要求从站应答
  • 地址域支持缩位寻址,即从若干低位起,剩余高位补AAH作为通配符进行读表操作,从站应答帧的地址域返回实际通信地址
  • 地址域传输时字节低字节在前,高字节在后

2.3 控制码

控制码长度为1个字节,控制码需转成8位的二进制码来解析命令,如0x11,对应的8位二进制码就是 00010001,对应下图则是表示

  • D7(0) :主站发出的命令帧

  • D6(0):总站正确应答

  • D5(0):无后续数据帧

  • D4-D0(10001):读数据

1728953628981

1728953323638

3.4 数据长度

1个字节,表示数据域的字节数。读数据时 L≤200,写数据时L≤50,L--=0表示无数据域

3.5 数据域

数据域包括数据标识、密码、、操作者代码、数据、帧序号等,其结构随控制码的功能而改变。传输时按字节进行加33H处理,接收方按字节进行减33H处理

*数据标识

数据标识编码用四个字节区分不同数据项,四字节分别用DI3、DI2、DI1和DI0代表,每字节采用十六进制编码。数据类型分为七类:电能量、最大需量及发生时间、变量、时间记录、参变量、冻结量、负荷记录

1728961333399

  • DI3

    DI3标识符 对应数据类型
    00 电能量
    01 最大需量及发生时间
    02 变量数据 (遥测等)
    03 事件记录
    04 参变量数据
    05 冻结量
    06 负荷记录

举例

发送端
需要发送            0x02 0x01 0x01 0x00
对应数据标识         DI3  DI2  DI1  DI0
需要发送的数据域     0x00 0x01 0x01 0x02  (发送时,数据标识低位在前,高位在后)
对应数据标识         DI0  DI1  DI2  DI3   (发送时,数据标识低位在前,高位在后)
实际发送数据域:     0x33 0x34 0x34 0x35  (发送端数据域中的数据需作+33H的操作)

接收端返回数据: 0x33 0x34 0x34 0x35 0x73 0x55 0x76 0x55 0x78 0x55
实际返回的数据: 0x00 0x01 0x01 0x02 0x40 0x22 0x43 0x22 0x45 0x22 (高位在后,接收端数据域中的数据需作-33H的操作)
拼接好高低位的数据标识  0x02 0x01 0x01 0x00
拼接好高低位的数据(假设数据格式为XXX.X)  ,则返回数数据为
224.5(2245,格式转为XXX.X) 
224.3(2243,格式转为XXX.X) 
224.0(2240,格式转为XXX.X) 

2.6 校验码 CS

占2个字节,从第一个帧起始符开始到校验码之前的所有各字节的模 256 的和,即各字节二进制算术和,不计超过 256 的溢出值。

    /**
     * @description: 计算校验码
     * @author WXP
     * @date 2024/10/11 14:44
     * @version 1.0
     */
    public static byte calculateChecksum(byte[] data) {
        int count = 0;
        int len = data.length - 2;
        for (int i = 0; i < len; i++) {
            count += data[i];
        }
        byte b = (byte) (count & 0xFF); // 0xFF 等于二进制 1111 1111 只取二进制后8位,既不计超过256的溢出值
        return b;
    }

2.7 结束符

占一个字节,固定为 16H,标识一帧信息的结束。

2.8 传输事项

  • 前导字节:在主站发送帧信息前,先发送4个字节 FEH,以唤醒接收方

  • 传输次序:所有数据项均先传送低位字节,后传送高位字节。

  • 传输响应:每次通信都是由主站向按信息帧地址域选择的从站发出请求命令帧开始,被请求的从站接收到命令后做出的响应

  • 差错控制:字节校验为偶校验,帧校验为纵向信息校验和,接收方无论检测到偶校验出错或纵向信息校验和出错,均放弃该信息帧,不予响应

3. 报文解析

发送:68 11 11 11 11 11 11 68 11 04 33 32 34 35 19 16
  • 68:帧起始符,标识这是一帧信息的开始
  • 11 11 11 11 11 11:电表地址,低位在前,高位在后,实际地址为反过来
  • 68:帧起始符,标识前面地址信息结束
  • 11:控制码,对应二进制为 00010001,按前面控制码规则为:主站发送的命令帧(0),从站正确应答(0),无后续数据帧(0),读电表数据(10001)
  • 04:数据域字节数,此处数据域字节数为4
  • 33 32 34 35:数据域,此为数据标识,高位在前,低位在后,传输时都进行了加33H的操作,实际数据为 02 01 FF 00
  • 19:校验码,通过前面算法可以算出
  • 16:结束符,标识一帧信息的结束
接收:68 11 11 11 11 11 11 68 91 0A 33 32 34 35 C8 55 CB 55 33 56 65 16
  • 68:帧起始符,标识这是一帧信息的开始

  • 11 11 11 11 11 11:电表地址,低位在前,高位在后,实际地址为反过来

  • 68:帧起始符,标识前面地址信息结束

  • 91:控制码,对应二进制为 10010001,按前面控制码规则为:从站发送的命令帧(1),从站正确应答(0),无后续数据帧(0),读电表数据(10001)

  • 0A:数据域字节数,此处数据域字节数为10

  • 33 32 34 35 C8 55 CB 55 33 56:数据域,高位在前,低位在后,传输时都进行了加33H的操作

    • 33 32 34 35 为数据标识,-33H并调整高低位后为 02 01 FF 00

    • C8 55 CB 55 33 56 为返回的数据,-33H并调整高低位后为 23 00 22 98 22 95,按(XXX.X)的数据格式则为 230.0 229.8 229.5

      1728959102093

  • 65:校验码,通过前面算法可以算出

  • 16:结束符,标识一帧信息的结束

4. 代码实例

此为Java实现的一个Demo,需导入 jSerialComm 串口通信的包。若接收的信息不完整,则调整接收报文前的线程睡眠时间,或者更改成用while循环监听串口数据流的回复。

<dependency>
    <groupId>com.fazecast</groupId>
    <artifactId>jSerialComm</artifactId>
    <version>2.9.2</version>
</dependency>
package org.xp;

import com.fazecast.jSerialComm.SerialPort;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author sam
 * @version 1.0
 * @description: DLT645Demo
 * @date 2024/10/11 10:16
 */
public class DLT645Demo {

    // 设备通讯地址
    private final static String address = "111111111111";
    // 控制码
    private static final int controlCode = 0x11;
    // 数据长度
    private static final int dataLength = 0x4;
    // 控制码中从机回复成功的功能码
    private static final String[] successCode = {"00000", "10001", "10010", "10011", "10100", "10101", "01000", "10110", "10111", "11000", "11001", "11010"
            , "11011", "11100", "11101", "00011"};


    // 帧起始符
    private static final int startCharacter = 0x68;

    // 结束符
    private static final int endCharacter = 0x16;

    // 数据加减操作
    private static final int codeChange = 0x33;

    public static void main(String[] args) {

        // 设置通信端口、波特率、数据大小、校验位、停止位
        SerialPort serialPort = SerialPort.getCommPort("COM3");
        serialPort.setBaudRate(2400);   // 此电表波特率为2400
        serialPort.setNumDataBits(8);
        serialPort.setParity(SerialPort.EVEN_PARITY);   // 偶校验
        serialPort.setNumStopBits(1);

        // 初始化发送数据
        ArrayList<Integer> dataList = new ArrayList<>();
        dataList.add(0x00);
        dataList.add(-0x01);
        dataList.add(0x01);
        dataList.add(0x02);


        byte[] request = {};

        // 打开串口,传输数据
        if (serialPort.openPort()) {
            // 发送
            request = buildDLT645Request(address, controlCode, dataLength, dataList);
            System.out.println("发送的报文:" + Arrays.toString(request));
            StringBuilder hexString = new StringBuilder();
            for (byte b : request) {
                hexString.append(toHex(b)).append(" ");
            }
            System.out.println(hexString);
            System.out.println(toHex(request[request.length - 2]));
            serialPort.writeBytes(request, request.length);
        }

        // 睡眠 300 毫秒,等待从机响应
        try {
            Thread.sleep(300);  // 若传输回来的数据不完整,需调整睡眠时间,回传的报文越长,需要睡眠的时间也要越长
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 接收
        byte[] bytes = new byte[255];
        serialPort.readBytes(bytes, bytes.length);
        System.out.println("接收的报文:" + Arrays.toString(bytes));
        // 将 接收的数据放进新的合适大小的byte数组,去掉开头4个FE Integer.parseInt(toHex(bytes[9])) 数据域长度
        byte[] response = Arrays.copyOfRange(bytes, 4, 4 + 10 + Integer.parseInt(toHex(bytes[13]), 16) + 2);
        StringBuilder hexString2 = new StringBuilder();
        for (byte b : response) {
            hexString2.append(toHex(b)).append(" ");
        }
        System.out.println("报文:" + hexString2);
        if (!hexString2.substring(hexString2.length() - 3, hexString2.length() - 1).equals("16")) {
            System.out.println("结束符的数据有误,未接收到结束符");
        }
        // 处理报文
        receiveMessage(response);

        // 关闭串口
        serialPort.closePort();
    }

    /**
     * @description: byte 字符转换成十六进制字符串
     * @param: b
     * @return:
     * @author WXP
     * @date: 2024/10/14 12:14
     */
    private static String toHex(byte b) {
        // Convert byte to unsigned int and then to hex string
        return String.format("%02X", b & 0xFF);
    }

    /**
     * @description: 构建645请求报文
     * @param: address
     * controlCode
     * dataLength
     * dataList
     * @return:
     * @author WXP
     * @date: 2024/10/15 10:29
     */
    private static byte[] buildDLT645Request(String address, int controlCode, int dataLength, List<Integer> dataList) {

        // 数据长度校验
        if (dataLength < 0) {
            return new byte[0];
        }
        if (dataList == null && dataLength > 0) {
            return new byte[0];
        }
        if (dataList != null && (dataList.isEmpty() && dataLength > 0 || dataList.size() != dataLength)) {
            return new byte[0];
        }

        byte[] bytes = new byte[dataList != null && !dataList.isEmpty() ? 12 + dataList.size() : 12];

        // 帧起始符
        bytes[0] = (byte) startCharacter;
        // 增加地址
        bytes[1] = (byte) Integer.parseInt(address.substring(0, 2), 16);
        bytes[2] = (byte) Integer.parseInt(address.substring(2, 4), 16);
        bytes[3] = (byte) Integer.parseInt(address.substring(4, 6), 16);
        bytes[4] = (byte) Integer.parseInt(address.substring(6, 8), 16);
        bytes[5] = (byte) Integer.parseInt(address.substring(8, 10), 16);
        bytes[6] = (byte) Integer.parseInt(address.substring(10, 12), 16);


        // 帧起始符
        bytes[7] = (byte) startCharacter;
        // 控制符
        bytes[8] = (byte) controlCode;
        // 数据长度
        bytes[9] = (byte) dataLength;
        // 数据域 发送端需 + 0x33 处理
        if (dataLength > 0) {
            for (int i = 0, j = 10; i < dataList.size(); i++, j++) {
                bytes[j] = (byte) (dataList.get(i) + codeChange);
            }
        }
        // 校验码
        bytes[bytes.length - 2] = calculateChecksum(bytes);
        // 结束符
        bytes[bytes.length - 1] = (byte) endCharacter;

        return bytes;
    }

    /**
     * @description: 接收从站回传报文
     * @param: bytes
     * @return:
     * @author WXP
     * @date: 2024/10/15 10:28
     */
    private static byte[] receiveMessage(byte[] bytes) {

        System.out.println("-----------接收------------------");

        // 帧起始符
        System.out.println("帧起始符:" + toHex(bytes[0]));
        // 地址解析
        byte[] addressArray = new byte[6];
        for (int i = 0; i < 6; i++) {
            addressArray[i] = bytes[6 - i];
        }
        StringBuilder address = new StringBuilder();
        for (byte b : addressArray) {
            address.append(toHex(b));
        }
        System.out.println("地址为:" + address);

        // 帧起始符
        System.out.println("帧起始符:" + toHex(bytes[7]));

        // 控制符
        parseControl(bytes[8]);

        // 数据长度
        // 检验报文中的数据长度和数据长度位是否一致
        System.out.println("数据长度 :" + Integer.toHexString(bytes[9]));

        // 数据域 发送端需 + 0x33 处理
        byte[] dataArray = new byte[bytes.length - 12];
        StringBuilder sb = new StringBuilder();
        if (!Byte.valueOf(bytes[8]).equals(Byte.valueOf("0"))) {
            for (int i = 0, j = 10; j < bytes.length - 2; i++, j++) {
                // -33H
                dataArray[i] = (byte) (bytes[j] - codeChange);
                sb.append(toHex(dataArray[i])).append(" ");
            }
            System.out.println("数据域:" + sb);
            System.out.println("数据标识:" + sb.substring(0, 12));
            System.out.println("实际数据:" + sb.substring(12, sb.length() - 1));
        }

        // 校验码
        System.out.println("校验码:" + toHex(bytes[bytes.length - 2]));
        if (toHex(calculateChecksum(bytes)).equals(toHex(calculateChecksum(bytes)))) {
            System.out.println("校验无误");
        }

        // 结束符
        System.out.println("结束符" + toHex(bytes[bytes.length - 1]));

        // 提取数据 高位在后 低位在前,根据实际返回值自行写逻辑解析
        String[] split = sb.substring(12, sb.length() - 1).split(" ");
        System.out.println("拆分后数据:" + Arrays.toString(split));
        System.out.println("A相电压:" + (Integer.parseInt(split[1]) * 10 + Float.parseFloat(split[0]) / 10));
        System.out.println("B相电压:" + (Integer.parseInt(split[3]) * 10 + Float.parseFloat(split[2]) / 10));
        System.out.println("C相电压:" + (Integer.parseInt(split[5]) * 10 + Float.parseFloat(split[4]) / 10));

        return bytes;
    }

    // 将十六进制字符串转换为二进制字符串
    public static String hexToBinary(String hex) {
        StringBuilder binary = new StringBuilder();
        // 遍历十六进制字符串的每一个字符
        for (int i = 0; i < hex.length(); i++) {
            // 获取当前字符的十六进制值(0-9, A-F)
            char c = hex.charAt(i);
            int hexValue = Character.digit(c, 16);
            // 将十六进制值转换为4位二进制字符串,并添加到结果中
            binary.append(Integer.toBinaryString(hexValue));
        }
        return binary.toString();
    }

    /**
     * @description: 计算校验码
     * @author WXP
     * @date 2024/10/11 14:44
     * @version 1.0
     */
    public static byte calculateChecksum(byte[] data) {
        int count = 0;
        int len = data.length - 2;
        for (int i = 0; i < len; i++) {
            count += data[i];
        }
        byte b = (byte) (count & 0xFF);
        return b;
    }

    /**
     * 解析控制码
     */
    public static void parseControl(byte control) {
        // 校验控制符是否为从站应答成功
        String binary = hexToBinary(toHex(control));
        boolean flag = false;
        for (String s : successCode) {
            if (binary.equals(s)) {
                flag = true;
                break;
            }
        }
        if (flag) {

            System.out.println("控制码:" + toHex(control));
            // D0-D4:功能码
            String function = Integer.toBinaryString(control & 0x1F);
            // D5:后续帧标志
            String next = String.format("%d", control >> 5 & 0x01);
            // D6:从站应答标志
            String response = String.format("%d", control >> 6 & 0x01);
            // D7:传输方向标志
            String direction = String.format("%d", control >> 7 & 0x01);

            // 一起解析
            System.out.println("功能码:" + function + ",后续帧标志:" + next + ",从站应答标志:" + response + ",传输方向标志:" + direction);
            if ("1".equals(response)) {
                System.out.println("从站异常应答");
                return;
            }

        } else {
            System.out.println("控制码错误");
        }


    }

}

5. 报文解析工具

645报文解析小工具,可以方便提取报文和理解报文。下面是百度网盘连接以及工具界面
链接:https://pan.baidu.com/s/1oMu9lLO5e__HHbYWnzEkqw
提取码:ojzw

主机发送的报文解析:68 11 11 11 11 11 11 68 11 04 33 32 34 35 19 16

1728960704124

从机回复的报文解析:68 11 11 11 11 11 11 68 91 0A 33 32 34 35 B9 55 BC 55 C4 55 D7 16

1728960663809

标签:11,入门,bytes,DLT645,System,2007,println,byte,数据
From: https://www.cnblogs.com/windowsxpxp/p/18467018

相关文章

  • 【网络安全工程师入门】从零基础到精通全面教程:一篇文章带你入门!
    前言想要成为网络安全工程师,却苦于没有方向,不知道从何学起的话,下面这篇网络安全入门教程可以帮你实现自己的网络安全工程师梦想,如果想学,可以继续看下去,文章有点长,希望你可以耐心看到最后!1、Web安全相关概念(2周)·熟悉基本概念(SQL注入、上传、XSS、、CSRF、一句话木马......
  • 中小型医院网站开发:Spring Boot入门
    2相关技术简介2.1Java技术Java是一种非常常用的编程语言,在全球编程语言排行版上总是前三。在方兴未艾的计算机技术发展历程中,Java的身影无处不在,并且拥有旺盛的生命力。Java的跨平台能力十分强大,只需一次编译,任何地方都可以运行。除此之外,它还拥有简单的语法和实用的类......
  • 全面解析:大模型技术从入门到精通的学习路径指南
    “技术学习无非涵盖三个方面,理论,实践和应用”大模型技术爆火至今已经有两年的时间了,而且大模型技术的发展潜力也不言而喻。因此,很多人打算学习大模型,但又不知道该怎么入手,因此今天就来了解一下大模型的学习路线。‍‍丁元英说:“透视社会有三个层面,技术,制度与文化”;同样的,......
  • 搞懂这些AI大模型名词,你也能轻松入门!
    大模型应用开发正在逐渐改变各个行业,但对技术小白来说,了解并掌握这些复杂的工具和概念非常重要。你是否觉得面对“LlamaIndex”、“Ollama”、“Anthropic”等术语无从下手?你是否在应用开发时被各种名词搞得晕头转向,不知道它们之间的区别与联系?我们将为你详细介绍这些关......
  • 网络安全自学入门:(超详细)从入门到精通学习路线&规划,学完即可就业_网络安全自学路线
    很多人上来就说想学习黑客,但是连方向都没搞清楚就开始学习,最终也只是会无疾而终!黑客是一个大的概念,里面包含了许多方向,不同的方向需要学习的内容也不一样。算上从学校开始学习,已经在网安这条路上走了10年了,无论是以前在学校做安全研究,还是毕业后在百度、360从事内核安全产......
  • 网络安全最厉害五个专业(非常详细),零基础入门到精通,看这一篇就够了
    文章目录1.网安方向介绍01、密码学与应用安全02、量子信息安全03、数据安全04、系统安全05、网络安全2.就业前景分析01、大厂安全部门02、安全公司03、高校==零基础入门黑客技术/网络安全==【----帮助网安学习,以下所有学习资料文末免费领取!----】大纲学习教程面试刷......
  • 大语言模型对普通人最实在的帮助(非常详细),零基础入门到精通,看这一篇就够了
    前言记得小时候流行过一类叫《十万个为什么》的书,听名字就知道这些书好像就是一个巨大无比的知识仓库,里面存放着无穷无尽的知识,可以满足孩子们的一切好奇心——但我小时候从没买过,可能没有什么求知欲吧。现在的孩子,似乎更没有必要买入一本《十万个为什么》了,并不是因为没......
  • 统计数字(2007年NOIP全国联赛提高组)
    题目描述某次科研调查时得到了n个自然数,每个数均不超过1500000000(1.5*109)。已知不相同的数不超过10000个,现在需要统计这些自然数各自出现的次数,并按照自然数从小到大的顺序输出统计结果。输入格式每组输入数据包含n+1行;第一行是整数n,表示自然数的个数;第2~n+1行,每行一个自......
  • Go 语言基准测试入门
    之前在写Java的文章的时候,如果想在本地进行某段代码的性能测试(通常是对比另外一段或者几段),就会用到基准测试框架JMH,也的确非常好用。虽然我学习Go语言有一段时间了,对于基准测试还没有涉猎,下面就分享Go语言的基准测试入门实践。什么是基准测试基准测试(Benchmarking)是一种通......
  • 基础架构补全—C语言基础入门篇(二)
    前言:    上一篇博客我仅简单对C语言语法基础进行了简单介绍,这次我将对其中遗漏知识进行补全及延申,但冰冻三尺非一日之寒,现在我也只不过是站在C语言的光辉大门前罢了,尚没有彻底迈出一步,希望各位能共励共勉,在接下来的学习中都能,鹏程发韧,培风图南,日进一卒,功不唐捐!1.注释......