[天龙/汉威]可变信息标志通信协议-向可变信息标志上载文件(JAVA版)

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 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值