1.代码如下
package com.xi.board.common;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.checksum.crc16.CRC16XModem;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* 通讯接口
* <p>
* 本协议用于河南天龙电子有限公司生产的可变信息标志(包括全点阵的可变限速标志)
* 与监控计算机之间的通信。可变信息标志操作系统 (CMSOS) 的版本应为第四版。可变信
* 息标志的通信接口为网络接口 RJ-45 和串型接口 RS-232。
*/
public class CommunicationUtils {
/**
* 数据帧 头
*/
private static String headHex = "02 ";
/**
* 数据帧 尾
*/
private static String tailHex = " 03";
/**
* 向可变信息标志上载文件的数据帧
*
* @param address 帧地址
* @param filePath 上载的文件
*/
public static List<String> getTxtFrame(String address, String filePath) {
address = HexUtil.encodeHexStr(address + "10"); 帧地址 + 帧类型
List<String> list = new ArrayList<>();//上载文件时,如文件长度超过 2048 字节,必须把文件分割成等于 2048 字节的若干段(最后一段为0-2047字节),每段依次发送 。
File file = FileUtil.file(filePath);
// System.out.println(FileUtil.readString(file, CharsetUtil.CHARSET_GBK));//打印文件内容
String fileName = file.getName();
String fileNameHexStr = HexUtil.encodeHexStr(fileName, CharsetUtil.CHARSET_GBK); //文件名 不定长 ASCII 码字符串
String bodyHeadHex = fileNameHexStr + "2b"; //分隔符 1 字节,0x2B,表明文件名的结束
long fileLength = file.length(); //获取文件指针偏移量
long l = fileLength / 2048 + 1;
for (int i = 0; i < l; i++) {
String fileLengthHex = StringUtils.leftPad(String.valueOf(i * 2048), 8, "0");//文件指针偏移; 4 字节十六进制数,先发高字节,后发低字节
String bodyHex = address + bodyHeadHex + fileLengthHex; //帧数据体
byte[] bytes = FileUtil.readBytes(file);
String encodeHexStr = HexUtil.encodeHexStr(bytes);
if (i + 1 == l) {
bodyHex += encodeHexStr.substring(i * 2048);
} else {
bodyHex += encodeHexStr.substring(i * 2048, (i + 1) * 2048);
}
//组装完整的一帧数据
String data = checkAndAssemble(bodyHex);
//添加进集合
list.add(data);
}
return list;
}
/**
* 向可变信息标志上载文件的数据帧(废弃-不实用)
*
* @param address 帧地址
* @param x 左上角x坐标
* @param y 左上角y坐标
* @param fontSize 字体大小(例如24x24点阵汉字就传24) 可选值 16 24 32 48
* @param red 红色RGB值
* @param green 绿色RGB值
* @param blue 蓝色RGB值
* @param content 文字内容
* @return
*/
public static List<String> getTxtFrame(String address, Integer x, Integer y, Integer fontSize, Integer red, Integer green, Integer blue, String content) {
address = HexUtil.encodeHexStr(address + "10"); //帧地址 + 帧类型
List<String> list = new ArrayList<>();//上载文件时,如文件长度超过 2048 字节,必须把文件分割成等于 2048 字节的若干段(最后一段为0-2047字节),每段依次发送 。
String fileNameHexStr = HexUtil.encodeHexStr("play.lst", CharsetUtil.CHARSET_GBK); //文件名 不定长 ASCII 码字符串
String bodyHeadHex = fileNameHexStr + "2b"; //分隔符 1 字节,0x2B,表明文件名的结束
try {
content = new String(content.getBytes("gbk"), "gbk");
} catch (UnsupportedEncodingException e) {
System.out.println("转GBK编码失败!");
e.printStackTrace();
}
String fileContent = "[list]\r\n" +
"item_no=1\r\n" +
"item0=500,1,2,\\C" + StringUtils.leftPad(x.toString(), 3, "0") + StringUtils.leftPad(y.toString(), 3, "0") +
"\\fs" + StringUtils.leftPad(fontSize.toString(), 2, "0") + StringUtils.leftPad(fontSize.toString(), 2, "0") +
"\\c" + StringUtils.leftPad(red.toString(), 3, "0") + StringUtils.leftPad(green.toString(), 3, "0") + StringUtils.leftPad(blue.toString(), 3, "0") + "000" +
content + "\r\n";
// System.out.println(fileContent);//打印文件内容
long fileLength = fileContent.length(); //获取文件指针偏移量
long l = fileLength / 2048 + 1;
for (int i = 0; i < l; i++) {
String fileLengthHex = StringUtils.leftPad(String.valueOf(i * 2048), 8, "0");//文件指针偏移; 4 字节十六进制数,先发高字节,后发低字节
String bodyHex = address + bodyHeadHex + fileLengthHex; //帧数据体
byte[] bytes = StrUtil.bytes(fileContent, CharsetUtil.CHARSET_GBK);
String encodeHexStr = HexUtil.encodeHexStr(bytes);
if (i + 1 == l) {
bodyHex += encodeHexStr.substring(i * 2048);
} else {
bodyHex += encodeHexStr.substring(i * 2048, (i + 1) * 2048);
}
//组装完整的一帧数据
String data = checkAndAssemble(bodyHex);
//添加进集合
list.add(data);
}
return list;
}
/**
* 帧校验及其帧数据组装加上头和尾
*
* @param bodyHex
*/
private static String checkAndAssemble(String bodyHex) {
//帧校验采用 16 位的 CRC 校验
String checkHex = checkHex(bodyHex);
//帧数据体
bodyHex += checkHex;
//帧数据或帧校验中如有某个字节等于帧头、帧尾 或 0x1B ,则在发送此帧时需转换为两个字节,即 0x1B 和 (此字节减去 0x1B)
bodyHex = hexEncodeHandle(bodyHex);
//格式化十六进制字符串
String frameHex = headHex + bodyHex + tailHex;
// System.out.println("帧数据:" + frameHex.toUpperCase());
return frameHex.toUpperCase();
}
/**
* 帧校验采用 16 位的 CRC 校验
*
* @param bodyHex
* @return
*/
private static String checkHex(String bodyHex) {
CRC16XModem crc16IBM = new CRC16XModem();
crc16IBM.update(HexUtil.decodeHex(bodyHex));
String checkHex = crc16IBM.getHexValue();
// System.out.println("CRC16校验值:" + checkHex);
return StringUtils.leftPad(checkHex,4,"0");
}
/**
* 帧数据或帧校验中如有某个字节等于帧头、帧尾 或 0x1B ,则在发送此帧时需转换为两个字节,即 0x1B 和 (此字节减去 0x1B)
* 0x02 转换为 0x1B, 0xE7
* 0x03 转换为 0x1B, 0xE8
* 0x1B 转换为 0x1B, 0x00
*
* @param bodyHex
* @return
*/
private static String hexEncodeHandle(String bodyHex) {
//格式化16进制(字节用空格隔开)
bodyHex = HexUtil.format(bodyHex).toLowerCase();
//数据或帧校验中如有某个字节等于帧头、帧尾 或 0x1B ,则在发送此帧时需转换为两个字节,即 0x1B 和 (此字节减去 0x1B)
bodyHex = bodyHex.replaceAll("1b", "1b 00").replaceAll("02", "1b e7").replaceAll("03", "1b e8");
return bodyHex;
}
/**
* 帧数据或帧校验中如有某个字节等于帧头、帧尾 或 0x1B ,则在发送此帧时需转换为两个字节,即 0x1B 和 (此字节减去 0x1B)
* 00x1B, 0xE7 转换为 x02
* 0x1B, 0xE8 转换为 0x03
* 0x1B, 0x00 转换为 0x1B
*
* @param bodyHex
* @return
*/
private static String hexDecodeHandle(String bodyHex) {
//格式化16进制(字节用空格隔开)
bodyHex = HexUtil.format(bodyHex.replaceAll(" ", "")).toLowerCase();
//数据或帧校验中如有某个字节等于帧头、帧尾 或 0x1B ,则在发送此帧时需转换为两个字节,即 0x1B 和 (此字节减去 0x1B)
bodyHex = bodyHex.replaceAll("1b 00", "1b").replaceAll("1b e7", "02").replaceAll("1b e8", "03");
return bodyHex.replace(" ", "");
}
/**
* 十六进制字符串转二进制字符串
*
* @param hexStr 十六进制字符串转二进制字符串
* @return
*/
private static String hexToBinaryString(String hexStr) {
StringBuilder stringBuilder = new StringBuilder();
String tmp = "";
boolean isHeadZero = true;
for (int i = 0; i < hexStr.length(); i++) {
tmp = "0000" + Integer.toBinaryString(Integer.parseInt(hexStr.substring(i, i + 1), 16));
String substring = tmp.substring(tmp.length() - 4);
if (isHeadZero && substring.equals("0000")) { //针对天龙可变信息标志通讯协议进行特殊处理
continue;
} else {
isHeadZero = false;
}
stringBuilder.append(substring);
}
return stringBuilder.toString();
}
/**
* 发送 取可变信息标志的当前故障 的帧数据
*
* @return
*/
public static String getErrorContent(String address) {
address = HexUtil.encodeHexStr(address + "01"); //帧地址 + 帧类型
String data = checkAndAssemble(address);
return data;
}
/**
* 解析 取可变信息标志的当前故障 的应答帧数据
* 注:
* 0
* 1保留
* 2控制器故障
* 3显示模组故障
* 4显示模组电源故障
* ……
*
* @param hexStr 应答帧数据
* @return
*/
public static List<String> analysisErrorContent(String hexStr) {
hexStr = hexDecodeHandle(hexStr);
List<String> data = new ArrayList<>();
//获取内容帧数据
String bodyHex = hexStr.substring(6, hexStr.length() - 6);
//16进制转字符串
String decodeHexStr = HexUtil.decodeHexStr(bodyHex, CharsetUtil.CHARSET_GBK);
for (int i = 0; i < decodeHexStr.length(); i++) {
String errorCode = decodeHexStr.substring(i, i + 1);
if (errorCode.equals("1")) { //每一位对应一种故障类型,如此位为1,则有故障;如此位为0,则无故障。
data.add(errorCode);//故障类型添加到数组
}
}
return data;
}
/**
* 取可变信息标志的当前显示内容
*
* @param address 帧地址
* @return
*/
public static String getBoardContent(String address) {
address = HexUtil.encodeHexStr(address + "97"); //帧地址 + 帧类型
String data = checkAndAssemble(address);
return data;
}
/**
* 解析 取可变信息标志的当前显示内容 的应答帧数据
*
* @param hexStr 应答帧数据
* @param isProd 是否生产环境
* @return
*/
public static HashMap<String, String> analysisBoardContent(String hexStr, Boolean isProd) {
hexStr = hexDecodeHandle(hexStr);
//获取内容帧数据
String bodyHex = hexStr.substring(6, hexStr.length() - 6);
//结果
HashMap<String, String> map = new HashMap<>();
if (isProd) {
//hex转字符串
String index = HexUtil.decodeHexStr(bodyHex.substring(0, 6), CharsetUtil.CHARSET_GBK); //序号
String time = HexUtil.decodeHexStr(bodyHex.substring(6, 16), CharsetUtil.CHARSET_GBK); //停留时间
String type = HexUtil.decodeHexStr(bodyHex.substring(16, 20), CharsetUtil.CHARSET_GBK); //出字方式
String speed = HexUtil.decodeHexStr(bodyHex.substring(20, 30), CharsetUtil.CHARSET_GBK); //出字速度
String content = HexUtil.decodeHexStr(bodyHex.substring(30), CharsetUtil.CHARSET_GBK); //显示字符串
map.put("index", index);
map.put("time", time);
map.put("type", type);
map.put("speed", speed);
map.put("content", content);
}else {
map.put("content", HexUtil.decodeHexStr(bodyHex, CharsetUtil.CHARSET_GBK));
}
return map;
}
public static List<String> getTxtFrameByFileContent(String address, String fileContent) {
address = HexUtil.encodeHexStr(address + "10"); //帧地址 + 帧类型
List<String> list = new ArrayList<>();//上载文件时,如文件长度超过 2048 字节,必须把文件分割成等于 2048 字节的若干段(最后一段为0-2047字节),每段依次发送 。
String fileNameHexStr = HexUtil.encodeHexStr("play.lst", CharsetUtil.CHARSET_GBK); //文件名 不定长 ASCII 码字符串
String bodyHeadHex = fileNameHexStr + "2b"; //分隔符 1 字节,0x2B,表明文件名的结束
fileContent = fileContent.replaceAll("\r\n","\n").replaceAll("\n","\r\n");
try {
fileContent = new String(fileContent.getBytes("gbk"), "gbk");
} catch (UnsupportedEncodingException e) {
System.out.println("转GBK编码失败!");
e.printStackTrace();
}
System.out.println(fileContent);//打印文件内容
long fileLength = fileContent.length(); //获取文件指针偏移量
long l = fileLength / 2048 + 1;
for (int i = 0; i < l; i++) {
String fileLengthHex = StringUtils.leftPad(String.valueOf(i * 2048), 8, "0");//文件指针偏移; 4 字节十六进制数,先发高字节,后发低字节
String bodyHex = address + bodyHeadHex + fileLengthHex; //帧数据体
byte[] bytes = StrUtil.bytes(fileContent, CharsetUtil.CHARSET_GBK);
String encodeHexStr = HexUtil.encodeHexStr(bytes);
if (i + 1 == l) {
bodyHex += encodeHexStr.substring(i * 2048);
} else {
bodyHex += encodeHexStr.substring(i * 2048, (i + 1) * 2048);
}
//组装完整的一帧数据
String data = checkAndAssemble(bodyHex);
//添加进集合
list.add(data);
}
return list;
}
}
2.play.lst文件内容
[list]
item_no=1
item0=500,1,2,\C000000\fs2424\c000255000000谨慎驾驶注意安全
下载地址为:https://download.csdn.net/download/houjiezhuang/87065257