一、设计概要
1、设计要求
学习PCT通信协议,设计一个打包解包小工具。
2、总体结构
二、设计原理
1、PCT通信协议格式
PCT通信协议规定:
(1)数据包由1字节模块ID+1字节数据头+1字节二级ID+6字节数据+1字节校验和构成,共计10字节。
(2)数据包中有6个数据,每个数据为1字节。
(3)模块ID的最高位bit7固定为0。
(4)模块ID的取值范围为0x00~0x7F,最多有128种类型。
(5)数据头的最高位bit7固定为1,数据头的低7位按照从低位到高位的顺序,依次存放二级ID的最高位bit7、数据1的最高位bit7、数据2的最高位bit7、数据3的最高位bit7、数据4的最高位bit7、数据5的最高位bit7和数据6的最高位bit7。
(6)校验和的低7位为模块ID+数据头+二级ID+数据1+数据2+…+数据6求和的结果(取低7位)。
(7)二级ID、数据1~数据6和校验和的最高位bit7固定为1。注意,并不是说二级ID、数据1~数据6和校验和只有7位,而是在打包后,它们的低7位位置不变,最高位均位于数据头中,因此,依然还是8位。
2、PCT打包过程
PCT通信协议的打包过程分为4步。
第1步,准备原始数据,原始数据由模块ID(0x00~0x7F)、二级ID、数据1~数据6组成,如下图所示。其中,模块ID的取值范围为0x00~0x7F,二级ID和数据的取值范围为0x00~0xFF。
第2步,依次取出二级ID、数据1~数据6的最高位bit7,将其存放于数据头的低7位,按照从低位到高位的顺序依次存放二级ID、数据1~数据6的最高位bit7,如下图所示。
第3步,对模块ID、数据头、二级ID、数据1~数据6的低7位求和,取求和结果的低7位,将其存放于校验和的低7位,如下图所示。
第4步,将数据头、二级ID、数据1~数据6和校验和的最高位置1,如下图所示。
3. PCT解包过程
PCT通信协议的解包过程也分为4步。
第1步,准备解包前的数据包,原始数据包由模块ID、数据头、二级ID、数据1~数据6、校验和组成,如下图所示。其中,模块ID的最高位为0,其余字节的最高位均为1。
第2步,对模块ID、数据头、二级ID、数据1~数据6的低7位求和,如下图所示,取求和结果的低7位与数据包的校验和低7位对比,如果两个值的结果相等,则说明校验正确。
第3步,数据头的最低位bit0与二级ID的低7位拼接之后作为最终的二级ID,数据头的bit1与数据1的低7位拼接之后作为最终的数据1,数据头的bit2与数据2的低7位拼接之后作为最终的数据2,以此类推,如下图所示。
第4步,解包后的结果,由模块ID、二级ID、数据1~数据6组成。其中,模块ID的取值范围为0x00~0x7F,二级ID和数据的取值范围为0x00~0xFF。
三、设计内容
学习PCT通信协议,设计一个打包解包小工具,在行编辑框中输入模块ID、二级ID及6字节数据后,通过“打包”按钮实现打包操作,并将打包结果显示到打包结果显示区。另外,还可以根据用户输入的10字节待解包数据,通过“解包”按钮实现解包操作,并将解包结果显示到解包结果显示区。
四、使用说明与执行效果
代码结构说明:
PackUnpackTool类:
该java类主要是用于定义图形化界面,该界面包括模块ID、二级ID,裸数据,将要解包的数据的输入框及打包数据的输出框,还有打包和解包按钮。
moduleIDField、subIDField、dataField:用于输入模块ID、二级ID和数据的文本框。
packResultArea、unpackDataField、unpackResultArea:用于显示打包结果、解包的数据和解包结果的文本区域。
mPackUnpack:PackUnpack 类的实例,用于打包和解包数据。
PackButtonListener:监听打包按钮的点击事件,获取输入的模块ID、二级ID和数据,调用 mPackUnpack.packData() 进行打包,并将结果显示在相应的文本区域中。
UnpackButtonListener:监听解包按钮的点击事件,获取输入的解包数据,调用 mPackUnpack.unpackData() 进行解包,并将结果显示在相应的文本区域中。
PackUnpack类:
主要用于处理pct打包和解包的数据处理;以下是代码结构的总结:
1.成员变量:
PackLen:数据包长度。
GotModID:标志是否获得正确的模块ID。
RestByte:剩余字节数。
mPackBuf:用于存储数据包的数组,包括模块ID、数据头、二级ID、数据和校验和。
2.构造函数:
初始化成员变量,将数据包数组中的元素初始化为0,设置PackLen、GotModID和`RestByte`为默认值。
3.打包数据:
packData方法负责打包数据,检查模块ID是否在指定范围内以及数据包长度是否正确,然后调用packWithCheckSum方法进行打包。
4.打包带校验和:
packWithCheckSum方法计算校验和并准备数据进行传输。
5.解包数据:
unpackData方法负责解包接收到的数据,首先检查模块ID,然后重建数据包。如果接收到完整的数据包,将调用unpackWithCheckSum方法。
6.解包带校验和:
unpackWithCheckSum方法验证校验和并重建数据包。
效果展示:
Java的swing图形初始化界面:
当模块ID或数据出错时
数据打包解包成功时:
附件(代码)
PackUnpack(实现方法类):
public class PackUnpack {
/**
* PackLen 数据包长度
* GotModID 获得正确的模块ID即为true,否则为false
* RestByte 剩余字节数
*/
private static int PackLen;
private static boolean GotModID;
private static int RestByte;
private int[] mPackBuf = new int[10];
/**
* @method 类的构造函数,初始化该模块
*/
public PackUnpack() {
//模块ID、数据头、二级ID、数据及校验和均清零
for (int i = 0; i < 10; i++) {
mPackBuf[i] = 0;
}
//数据包的长度默认为0,获取到数据包ID标志默认为false,剩余的字节数默认为0
PackLen = 0;
GotModID = false;
RestByte = 0;
}
/**
* 解包方法返回
* @return
*/
public int[] getUnpackResult() {
return (mPackBuf);
}
/**
* 打包方法封装
* @param packet
*/
public void packData(int[] packet) {
//模块ID必须在0x00-0x7F之间, packDin[0]为模块ID
if (packet[0] < 0x80) {
if (packet.length == 10) {
packWithCheckSum(packet);
}
}
}
private void packWithCheckSum(int[] packet) {
int dataHead; //数据头,位于模块ID之后
int checkSum; //校验和,数据包的最后一个字节
checkSum = packet[0]; //取出模块ID,赋值给校验和
dataHead = 0; //数据头清零
for (int i = 8; i > 1; i--) {
dataHead <<= 1; //数据头左移
packet[i] = ((packet[i - 1]) | 0x80); //最高位置为1
checkSum += packet[i]; //数据加到校验和
dataHead |= (((packet[i - 1]) & 0x80) >> 7); //取出原始数据的最高位,与dataHead相或
}
packet[1] = (dataHead | (0x80)); //数据头的最高位也要置为1
checkSum += packet[1]; //将数据头加到校验和
packet[9] = ((checkSum | 0x80) & 0x0ff); //校验和的最高位也要置为1
}
//解包数据方法响应
public boolean unpackData(int data) {
boolean findPack = false;
//已经接收到模块ID
if (GotModID) {
//非模块ID(数据头、二级ID、数据、校验和)必须大于或等于0X80
if (data >= 0x80) {
//存储包括除模块ID之外的9个字节,因为第一个字节是模块ID
mPackBuf[PackLen] = data;
PackLen++; //包长递增
RestByte--; //剩余字节数递减
//已经接收到完整的数据包
if (RestByte <= 0 && PackLen == 10) {
//接收到完整数据包后尝试解包
findPack = unpackWithCheckSum(mPackBuf);
//清除获取到模块ID标志,即重新判断下一个数据包
GotModID = false;
}
} else {
GotModID = false;
}
} else if (data < 0x80) {
//如果当前的数据小于0x80,将其视为模块ID
RestByte = 9; //包剩余字节为9
PackLen = 1; //当前包长为1
mPackBuf[0] = data;//数据包的模块ID
GotModID = true; //表示已经接收到模块ID
}
return findPack; //如果获取到完整的数据包,并解包成功,findPack为true,否则为false
}
//解包方法
private boolean unpackWithCheckSum(int[] packet) {
int dataHead; //数据头,位于模块ID之后
int checkSum; //校验和,数据包的最后一个字节
checkSum = packet[0]; //取出模块ID,加到校验和
dataHead = packet[1]; //取出数据头,赋给dataHead
checkSum += dataHead; //将数据头加到校验和
for (int i = 1; i < 8; i++) {
checkSum += packet[i + 1]; //将数据依次加到校验和
packet[i] = ((packet[i + 1] & 0x7F) | ((dataHead & 0x01) << 7)); //还原二级ID和数据
dataHead >>= 1; //数据头右移一位
}
return (checkSum & 0x7F) == ((packet[9]) & 0x7F);
}
}
PackUnpackTool(图形界面)
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
public class PackUnpackTool extends JFrame {
private JTextField moduleIDField, subIDField, dataField, unpackDataField;
private JTextArea packResultArea, unpackResultArea;
private PackUnpack mPackUnpack;
public PackUnpackTool() {
mPackUnpack = new PackUnpack();
setTitle("PCT打包解包工具");
setSize(600, 400);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new GridLayout(2, 2));
// 创建打包区域
JPanel packPanel = new JPanel(new GridLayout(4, 2));
Font font = new Font("宋体", Font.PLAIN, 20);
JLabel moduleIDLabel = new JLabel("模块ID:");
moduleIDLabel.setFont(font);
moduleIDField = new JTextField();
moduleIDField.setFont(font);
JLabel subIDLabel = new JLabel("二级ID:");
subIDLabel.setFont(font);
subIDField = new JTextField();
subIDField.setFont(font);
JLabel dataLabel = new JLabel("原始数据 (6 bytes):");
dataLabel.setFont(font);
dataField = new JTextField();
dataField.setFont(font);
JButton packButton = new JButton("打包数据");
packButton.setFont(font);
packResultArea = new JTextArea();
packResultArea.setFont(font);
//不可输出状态
packResultArea.setEditable(false);
packPanel.add(moduleIDLabel);
packPanel.add(moduleIDField);
packPanel.add(subIDLabel);
packPanel.add(subIDField);
packPanel.add(dataLabel);
packPanel.add(dataField);
packPanel.add(packButton);
packPanel.add(packResultArea);
// 创建解包区域
JPanel unpackPanel = new JPanel(new GridLayout(2, 2));
JLabel unpackDataLabel = new JLabel("解包的数据 (10 bytes):");
unpackDataLabel.setFont(font);
unpackDataField = new JTextField();
unpackDataField.setFont(font);
JButton unpackButton = new JButton("解包");
unpackButton.setFont(font);
unpackResultArea = new JTextArea();
unpackResultArea.setFont(font);
unpackResultArea.setEditable(false);
unpackPanel.add(unpackDataLabel);
unpackPanel.add(unpackDataField);
unpackPanel.add(unpackButton);
unpackPanel.add(unpackResultArea);
// 添加事件监听
packButton.addActionListener(new PackButtonListener());
unpackButton.addActionListener(new UnpackButtonListener());
// 添加到主窗口
add(packPanel);
add(unpackPanel);
setVisible(true);
}
class PackButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
int[] buffer = new int[10];
String moduleIDText = moduleIDField.getText();
String subIDText = subIDField.getText();
String dataText = dataField.getText();
try {
if (!moduleIDText.isEmpty()) {
int moduleID = Integer.parseInt(moduleIDText, 16);
if (moduleID > 0x7F) {
packResultArea.setText("输入模块ID超出范围");
return; // Stop further processing
}
buffer[0] = moduleID;
}
if (!subIDText.isEmpty()) {
buffer[1] = Integer.parseInt(subIDText, 16);
}
if (!dataText.isEmpty()) {
String[] dataBytes = dataText.split(" ");
for (int i = 0; i < dataBytes.length; i++) {
buffer[i + 2] = Integer.parseInt(dataBytes[i], 16);
}
}
mPackUnpack.packData(buffer);
StringBuilder packedResultBuilder = new StringBuilder();
for (int value : buffer) {
packedResultBuilder.append(Integer.toHexString(value)).append(" ");
}
packResultArea.setText(packedResultBuilder.toString().toUpperCase());
} catch (NumberFormatException ex) {
packResultArea.setText("打包数据格式错误");
}
}
}
class UnpackButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
int[] buffer = new int[10];
String unpackDout;
unpackResultArea.setText("");
String[] unpackDin = unpackDataField.getText().toString().split(" ");
if (unpackDin.length != 10) {
unpackResultArea.setText("解包数据的格式错误");
return;
}
for (int i = 0; i < unpackDin.length; i++) {
buffer[i] = Integer.parseInt(unpackDin[i], 16);
if(mPackUnpack.unpackData(buffer[i])) {
buffer = mPackUnpack.getUnpackResult();
for(int j = 0; j < 8; j++) {
unpackDout = Integer.toHexString(buffer[j]);
if ((buffer[j] < 0x10)) {
unpackDout = "0" + unpackDout;
}
unpackResultArea.append(unpackDout.toUpperCase());
unpackResultArea.append(" ");
}
}
}
}
}
public static void main(String[] args) {
new PackUnpackTool();
}
}