package com.szpg.plc.protocol.fins.frame; import org.apache.log4j.Logger; import com.szpg.plc.protocol.fins.FINSConstants; import com.szpg.plc.util.ByteUtil; import com.szpg.plc.util.Bytes; /** * FINS协议的帧结构 * 暂时用到的有握手命令和读取命令两大类 * * 两种命令都包括命令(从客户端到服务端)以及命令的响应(从服务端到客户端)。其中,PC端表示客户端,PLC端表示服务端 * 1握手命令帧结构 * 46494E53 0000000C 00000000 00000000 000000C7 * 含义 FINS包头 数据长度 功能码 错误码 本机地址 * * 2握手命令响应帧结构 * 46494E53 00000010 00000001 00000000 000000C7 00000001 * 含义 FINS包头 数据长度 功能码 错误码 本机地址 PLC地址 * * 3读取命令帧结构 * 46494E53 0000001A 00000002 00000000 800002 000100 00C700 00 0101 82 000000 0002 * 含义 FINS包头 数据长度 功能码 错误码 ICF/ DNA/ SNA/ SID 读取 DM区 起始地址 读取数(WORD) * RSV/ DA1/ SA1/ * GCT DA2 SA2 * * 4读取命令响应帧结构 * 46494E53 0000001A 00000002 00000000 C00002 00C700 000100 00 0101 0000 C0004541 * 含义 FINS包头 数据长度 功能码 错误码 ICF/ DNA/ SNA/ SID 读取 END CODE/ 两个字(WORD)数据 * RSV/ DA1/ SA1/ 正常读取 * GCT DA2 SA2 * * 5写命令帧结构 * 46494E53 0000001E 00000002 00000000 800002 000100 00C700 00 0102 82 000000 0002 C0004541 * 含义 FINS包头 数据长度 功能码 错误码 ICF/ DNA/ SNA/ SID 写 DM区 起始地址 写入数(WORD) 写入的数据 * RSV/ DA1/ SA1/ * GCT DA2 SA2 * * 6写命令响应帧结构 * 46494E53 00000016 00000002 00000000 C00002 00C700 000100 00 0102 0000 * 含义 FINS包头 数据长度 功能码 错误码 ICF/ DNA/ SNA/ SID 写入 END CODE/ * RSV/ DA1/ SA1/ 写入正常 * GCT DA2 SA2 * * @author admin * */ public class FINSByteFrame { /**************** 帧头部数据区 ****************/ // 起始字符FINS的ASCII码, 四字节 public static byte[] HEADER = new byte[] {0x46, 0x49, 0x4E, 0x53}; // 用户数据区长度字符, 四字节 public byte[] LENGTH = new byte[4]; // 功能码,四字节 public byte[] COMMAND = new byte[4]; // 错误码,四字节 public byte[] ERROR_CODE = new byte[4]; /**************** 用户数据区DATA ****************/ public byte[] TEXT_CONTROL_BODY; public byte[] TEXT_DATA_BODY; /**************** 控制域数据区 ****************/ public byte TEXT_CONTROL_ICF; public byte TEXT_CONTROL_RSV = FINSConstants.RSV; public byte TEXT_CONTROL_GCT = FINSConstants.GCT; public byte TEXT_CONTROL_DNA; public byte TEXT_CONTROL_DA1; public byte TEXT_CONTROL_DA2; public byte TEXT_CONTROL_SNA; public byte TEXT_CONTROL_SA1; public byte TEXT_CONTROL_SA2; public byte TEXT_CONTROL_SID = FINSConstants.SID; public byte[] FINS_COMMAND_CODE = new byte[2]; public byte MEMORY_AREA_CODE; public byte[] BEGIN_ADDRESS = new byte[3]; public byte[] NO_ITEMS = new byte[2]; // 用于上行响应帧 public boolean valid; private final Logger logger = Logger.getLogger(this.getClass().getName()); /** * 构造方法 由帧字节解析为帧对象 * 用于验证从PLC返回的消息 * * @param byteMessage */ public FINSByteFrame(byte[] byteMessage) { if (checkHearder(byteMessage) == false) { this.valid = false; logger.debug("帧头不是FINS" + ByteUtil.binToHexString(byteMessage, 0, 8)); } else if (checkLength(byteMessage) == false) { this.valid = false; logger.debug("帧长度不符" + ByteUtil.binToHexString(byteMessage, 8, 16)); } else if (byteMessage.length > 26 && checkControlICF(byteMessage) == false) { this.valid = false; logger.debug("帧ICF字节不符" + ByteUtil.binToHexString(new byte[] {byteMessage[16]})); } else if (checkTextData(byteMessage) == false) { this.valid = false; logger.debug("帧用户数据域不符" + ByteUtil.binToHexString(byteMessage, 16, byteMessage.length * 2)); } else { this.valid = true; } } /** * 构造方法 DATA = LOCALHOST.NODE * 适用于上位机发送握手命令 * * @param sNode 本机IP地址的末字节 */ public FINSByteFrame(String sNode) { this.TEXT_CONTROL_BODY = null; // 控制域为空 // 用户数据域为本机地址的末位 this.TEXT_DATA_BODY = ByteUtil.hexStringToBytes(sNode, 4); // 计算帧长度并赋值 this.LENGTH = ByteUtil.intToBins(length(), 4); } /** * 构造方法 * 适用于读取内存命令 * * @param dest 目标地址 * @param sour 源地址 * @param sid 服务号 * @param memArea 内存区域代码 * @param start 读取的起始地址 * @param count 读取的长度(以word计,长度为2个字节) */ public FINSByteFrame(String dest, String sour, String sid, byte memArea, byte[] start, int count) { this.TEXT_CONTROL_BODY = FINSByteFrameTool.buildFrameControl(dest, sour, sid); // 构造控制域 // 设置读取内存命令的发送命令功能码00000002 this.COMMAND = FINSConstants.COMMAND_COMMAND; // 设置错误码为00000000 this.ERROR_CODE = FINSConstants.ERROR_NORMAL; // 构造用户数据域的内容 Bytes data = new Bytes(); data.append(FINSConstants.COMMAND_MEMORY_READ); data.append(memArea); data.append(start); data.append(ByteUtil.intToBins(count, 2)); // 为用户数据域赋值 this.TEXT_DATA_BODY = data.toBytes(); // 计算帧长度并赋值 this.LENGTH = ByteUtil.intToBins(length(), 4); } /** * 构造方法 * 适用于写内存命令 * * @param dest * @param sour * @param sid * @param memArea * @param start * @param count * @param dataBody */ public FINSByteFrame(String dest, String sour, String sid, byte memArea, byte[] start, int count, byte[] dataBody) { this.TEXT_CONTROL_BODY = FINSByteFrameTool.buildFrameControl(dest, sour, sid); // 构造控制域 // 构造用户数据域的内容 Bytes data = new Bytes(); data.append(FINSConstants.COMMAND_MEMORY_WRITE); data.append(memArea); data.append(start); data.append(ByteUtil.intToBins(count, 2)); data.append(dataBody); // 为用户数据域赋值 this.TEXT_DATA_BODY = data.toBytes(); // 计算帧长度并赋值 this.LENGTH = ByteUtil.intToBins(length(), 4); } /** * 判断帧起始字符 * * @param byteMessage * @return */ private boolean checkHearder(byte[] byteMessage) { if (byteMessage.length < 4) { return false; } if (byteMessage[0] == FINSByteFrame.HEADER[0] && byteMessage[1] == FINSByteFrame.HEADER[1] && byteMessage[2] == FINSByteFrame.HEADER[2] && byteMessage[3] == FINSByteFrame.HEADER[3]) return true; else return false; } /** * 判断帧长度 * * @param byteMessage * @return */ private boolean checkLength(byte[] byteMessage) { if (byteMessage.length < 8) { return false; } if (byteMessage.length == ByteUtil.binToInt(FINSByteFrameTool.getFrameLength(byteMessage)) + 8) { this.LENGTH = FINSByteFrameTool.getFrameLength(byteMessage); return true; } else { return false; } } /** * 判断ICF字节 * 从PLC端返回的消息该字节D7-D6为1 * * @param byteMessage * @return */ private boolean checkControlICF(byte[] byteMessage) { byte icf = byteMessage[16]; if ((icf & 0xC0) == 0xC0) { Bytes ctrlBytes = new Bytes(); for (int i = 16; i < 26; i++) { ctrlBytes.append(byteMessage[i]); } // 为控制域赋值 this.TEXT_CONTROL_BODY = ctrlBytes.toBytes(); this.TEXT_CONTROL_ICF = this.TEXT_CONTROL_BODY[0]; this.TEXT_CONTROL_RSV = FINSConstants.RSV; this.TEXT_CONTROL_GCT = FINSConstants.GCT; this.TEXT_CONTROL_DNA = this.TEXT_CONTROL_BODY[3]; this.TEXT_CONTROL_DA1 = this.TEXT_CONTROL_BODY[4]; this.TEXT_CONTROL_DA2 = this.TEXT_CONTROL_BODY[5]; this.TEXT_CONTROL_SNA = this.TEXT_CONTROL_BODY[6]; this.TEXT_CONTROL_SA1 = this.TEXT_CONTROL_BODY[7]; this.TEXT_CONTROL_SA2 = this.TEXT_CONTROL_BODY[8]; this.TEXT_CONTROL_SID = this.TEXT_CONTROL_BODY[9]; return true; } else return false; } private boolean checkTextData(byte[] byteMessage) { Bytes dataBytes = new Bytes(); if (byteMessage.length < 26) { // 握手命令响应,数据域从第16个字节开始 for (int i = 16; i < byteMessage.length; i++) { dataBytes.append(byteMessage[i]); } } else { // 读取命令,数据域从第26个字节开始 for (int i = 26; i < byteMessage.length; i++) { dataBytes.append(byteMessage[i]); } } // 为数据域赋值 this.TEXT_DATA_BODY = dataBytes.toBytes(); return true; } /** * 计算帧长度 * * @return */ private int length() { int len = 0; // 用控制域是否有内容来判断是握手命令还是读写命令 if (null == this.TEXT_CONTROL_BODY) { len = 4 + // 功能码长度 4 + // 错误码长度 4; //本机地址的末位 } else { len = 4 + // 功能码长度 4 + // 错误码长度 this.TEXT_CONTROL_BODY.length + // 控制域长度[10个字节] this.TEXT_DATA_BODY.length; // 用户数据域长度 } return len; } /** * 生成标准字节消息 * * @return */ public byte[] toBytes() { byte[] frame = null; Bytes bytes = new Bytes(); bytes.append(FINSByteFrame.HEADER) // 帧起始字符 .append(this.LENGTH) // 帧用户数据区长度字节 .append(this.COMMAND) // 命令码 .append(this.ERROR_CODE) // 错误码 .append(this.TEXT_CONTROL_BODY) //控制域数据区 .append(this.TEXT_DATA_BODY); // 用户区数据 frame = bytes.toBytes(); // 最后判断帧长度是否正确 if (frame.length == ByteUtil.binToInt(this.LENGTH) + 8) { return frame; } else { return null; } } }