北大青鸟 JBF293K接口卡 RS232/485 V1.5的协议实现

最近在和消防设备对接,花了几天功夫研究了青鸟的对接协议,串口协议实现借助了

Java实现串口通讯_java modbus开发-CSDN博客

协议的实现在注释掉的方法 receiveDataHandler里面,

调试采用了串口调试器:cktszs.rar和虚拟化串口工具 vspd6.9.rar(自己搜索下载看怎么用吧)

自己测试过的字串,供有需要的同学参考

82 38 30 32 34 30 38 39 3B 30 31 31 31 30 33 30 38 31 30 30 34 30 38 3C 3D 83
控制器:36 回路:8 部位号: 155 设备类型: 1 上报错误码: 128, 时间:17年 3月 8日 16时 4分 8秒

82 35 31 30 34 3F 32 34 31 30 30 30 30 30 30 30 30 30 30 32 31 32 34 3C 3D 83
控制器:4 回路:242 部位号: 65 设备类型: 0 上报错误码: 81, 时间:0年 0月 0日 0时 33分 36秒

82 3F 3A 30 32 30 3B 30 32 30 32 31 31 30 33 30 39 30 3E 30 3D 31 3B 3C 3D 83
气体灭火 控制器:2 信息类型:11 盘号:1 区号: 2 设备类型: 2 上报错误码: 250, 时间:17年 3月 9日 14时 13分 27秒

82 3F 3B 32 34 30 31 3F 3E 3A 31 31 31 30 33 31 37 30 37 30 37 31 3F 31 37 83
防火门 控制器:36 回路:1 部位:254 门类型:1 门状态:10 上报错误码: 251, 时间:17年 3月 23日 7时 7分 31秒

82 3F 3C 32 34 32 30 3C 37 31 32 31 31 30 34 31 37 30 37 30 37 31 3F 37 32 83
电器火灾 控制器:36 回路:32 部位:199 电气火灾类型:2 电气火灾状态:1 上报错误码: 252, 时间:17年 4月 23日 7时 7分 31秒

82 3F 3D 33 3F 30 31 3F 3C 32 31 31 31 30 35 31 38 30 3F 32 3D 30 31 3C 35 83
消防电源 控制器:63 回路:1 部位:252 探测器类型:1 探测器状态:2 上报错误码: 253, 时间:17年 5月 24日 15时 45分 1秒

代码:


import lombok.extern.slf4j.Slf4j;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.springframework.stereotype.Component;
import purejavacomm.CommPortIdentifier;
import purejavacomm.SerialPort;
import purejavacomm.SerialPortEvent;
import purejavacomm.SerialPortEventListener;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
@Slf4j
public class SerialPortUtils implements SerialPortEventListener {
private String PORT_NAME ;
private int BIT_RATE = 9600;
public static final int DATA_BITS = SerialPort.DATABITS_8;
public static final int STOP_BIT = SerialPort.STOPBITS_1;
public static final int PARITY_BIT = SerialPort.PARITY_NONE;

public SerialPort serialPort;
public SerialPortReceiver dataReceiver;
private InputStream in;
private OutputStream out;
// private static SerialPortUtils serialPortUtil;
private static HashMap<String, SerialPortUtils> serialMap = new HashMap<>(); // 如果通过串口服务器连接串口,就会存在多个串口同时打开的情况
// 保存串口返回信息
public String data;
// 保存串口返回信息十六进制
public String dataHex;

private SerialPortUtils(String PORT_NAME, int baudRate) {
this.PORT_NAME = PORT_NAME;
this.BIT_RATE = baudRate;
this.dataReceiver = null;
}

public static synchronized SerialPortUtils getInstance(String PORT_NAME, int baudRate) {
SerialPortUtils port;

port = serialMap.get(PORT_NAME);
if (port != null)
{
// 还需要判断波特率
if (port.BIT_RATE == baudRate)
return port;

// 波特率不同,需要删除旧的重新创建
port.close();
serialMap.remove(PORT_NAME);
port = new SerialPortUtils(PORT_NAME, baudRate);
port.init();
serialMap.put(PORT_NAME, port);
return port;
}

// 没有同名串口,重新创建
port = new SerialPortUtils(PORT_NAME, baudRate);
port.init();
serialMap.put(PORT_NAME, port);

return port;
}

public void init( ) {
try {
if(StringUtils.isEmpty(PORT_NAME)){
log.error("init PORT_NAME is null");
}else {
log.info("init PORT_NAME is :{}",PORT_NAME);
}
CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(PORT_NAME);
if (portIdentifier.isCurrentlyOwned()) {
log.error("Port is currently in use");
} else if (portIdentifier.getPortType() == 1) {
serialPort = (SerialPort) portIdentifier.open(PORT_NAME, 1000);
serialPort.setSerialPortParams(BIT_RATE, DATA_BITS, STOP_BIT, PARITY_BIT);

in = serialPort.getInputStream();
out = serialPort.getOutputStream();

serialPort.addEventListener(this);
serialPort.notifyOnDataAvailable(true);
} else {
log.error("Error: Only serial ports are handled");
}
} catch (Exception e) {
log.error("init failed",e);
}
}
public void addDataReceiver(SerialPortReceiver receiver) {
dataReceiver = receiver;
}

@Override
public void serialEvent(SerialPortEvent serialPortEvent) {
switch (serialPortEvent.getEventType()) {
case SerialPortEvent.DATA_AVAILABLE:
receive();
break;
}
}

public void send(String message) {
try {
log.info("sendmsg:{}",message);
byte[] bytes = hexStrToByteArray(message);
out.write(bytes);
// Thread.sleep(1000);
} catch (Exception e) {
log.error("send failed",e);
}
}

public void receive() {
byte[] readBuffer;

try {
in = serialPort.getInputStream();
// 通过输入流对象的available方法获取数组字节长度
readBuffer = new byte[in.available()];
// 从线路上读取数据流
int len = 0;
// int read = in.read(readBuffer);
while ((len = in.read(readBuffer)) != -1) {
// 直接获取到的数据
data = new String(readBuffer, 0, len).trim();
// 转为十六进制数据
dataHex = bytesToHexString(readBuffer);
// System.out.println("data:" + data);
// System.out.println("dataHex:" + dataHex);// 读取后置空流对象
in.close();
in = null;
break;
}

if (dataReceiver != null)
dataReceiver.receiveDataHandler(readBuffer, dataHex);
// receiveDataHandler(readBuffer, dataHex);
} catch (IOException e) {
System.out.println(("读取串口数据时发生IO异常"));
}
}
/*
public void receiveDataHandler(byte[] readBuffer, String readDataHex)
{
// 消防告警的主动上报,参考青鸟JBF 293K RS232/485通讯协议V1.5
byte[] byteArray = readBuffer;

if (byteArray.length != 26) // 标准长度26, 注意:电气火灾部件类型为0xff的扩展协议暂时不支持
{
log.error("bad alarm info: {}", readDataHex);
return;
}

switch (byteArray[0])
{
case (byte) 0x82: // 起始位 0x82,结束位0x83
if (byteArray[25] != (byte) 0x83)
{
log.error("bad alarm info: {}", readDataHex);
break;
}
int errorCode = bytesToInt(byteArray[1], byteArray[2]);
switch (errorCode)
{
case 0x00: //普通报警
case 0x69:
case 0x09:
case 0xEF:
case 0x01:
case 0x0B:
case 0x0A:
case 0x80:
case 0x81:
case 0x82:
case 0x83:
case 0x84:
case 0x85:
case 0x86:
case 0x87:
case 0x88:
case 0x8b:
case 0x8c:
case 0x8d:
case 0x90:
case 0x91:
case 0x97:
case 0x98:
{
int controlId = bytesToInt(byteArray[3], byteArray[4]);
int lineId = bytesToInt(byteArray[5], byteArray[6]);
int positionId = bytesToInt(byteArray[7], byteArray[8]);
int deviceType = bytesToInt(byteArray[9], byteArray[10]);
int year = bytesToInt(byteArray[11], byteArray[12]);
int month = bytesToInt(byteArray[13], byteArray[14]);
int day = bytesToInt(byteArray[15], byteArray[16]);
int hour = bytesToInt(byteArray[17], byteArray[18]);
int minutes = bytesToInt(byteArray[19], byteArray[20]);
int second = bytesToInt(byteArray[21], byteArray[22]);
if (errorCode == 0x8b) //模拟报警
lineId -= 1;

log.info("普通报警 控制器:{} 回路:{} 部位号: {} 设备类型: {} 上报错误码: {}, 时间:{}年 {}月 {}日 {}时 {}分 {}秒",
controlId, lineId, positionId, deviceType, errorCode, year, month, day, hour, minutes, second);
break;
}

case 0x51: // 多线手动,回路和部位特殊处理
case 0x52:
case 0x53:
case 0x54:
case 0x55:
case 0x56:
case 0x57:
case 0x58:
case 0x59:
case 0x5A:
{
int controlId = bytesToInt(byteArray[3], byteArray[4]);
int lineId = bytesToInt(byteArray[5], byteArray[6]);
int positionId = bytesToInt(byteArray[7], byteArray[8]);
int deviceType = bytesToInt(byteArray[9], byteArray[10]);
int year = bytesToInt(byteArray[11], byteArray[12]);
int month = bytesToInt(byteArray[13], byteArray[14]);
int day = bytesToInt(byteArray[15], byteArray[16]);
int hour = bytesToInt(byteArray[17], byteArray[18]);
int minutes = bytesToInt(byteArray[19], byteArray[20]);
int second = bytesToInt(byteArray[21], byteArray[22]);

int multiLineId = 0; //多线盘号
multiLineId = positionId / 8 + 1;
positionId = positionId % 8;

log.info("多线盘报警 控制器:{} 多线盘号:{} 专线号:{} 部位号: {} 设备类型: {} 上报错误码: {}, 时间:{}年 {}月 {}日 {}时 {}分 {}秒",
controlId, multiLineId, lineId, positionId, deviceType, errorCode, year, month, day, hour, minutes, second);
break;
}

case 0xFA: // 气体灭火1
{
int controlId = bytesToInt(byteArray[3], byteArray[4]);
int infoType = bytesToInt(byteArray[5], byteArray[6]);
int positionId = bytesToInt(byteArray[7], byteArray[8]);
int deviceType = bytesToInt(byteArray[9], byteArray[10]);
int year = bytesToInt(byteArray[11], byteArray[12]);
int month = bytesToInt(byteArray[13], byteArray[14]);
int day = bytesToInt(byteArray[15], byteArray[16]);
int hour = bytesToInt(byteArray[17], byteArray[18]);
int minutes = bytesToInt(byteArray[19], byteArray[20]);
int second = bytesToInt(byteArray[21], byteArray[22]);

int areaId = 0; // 区号
areaId = positionId % 4;
positionId = positionId / 4 + 1;
log.info("气体灭火 控制器:{} 信息类型:{} 盘号:{} 区号: {} 设备类型: {} 上报错误码: {}, 时间:{}年 {}月 {}日 {}时 {}分 {}秒",
controlId, infoType, positionId, areaId, deviceType, errorCode, year, month, day, hour, minutes, second);
break;
}

case 0x70:
case 0x71:
case 0x72:
{
int controlId = bytesToInt(byteArray[3], byteArray[4]);
int infoType = bytesToInt(byteArray[5], byteArray[6]);
int positionId = bytesToInt(byteArray[7], byteArray[8]);
int deviceType = bytesToInt(byteArray[9], byteArray[10]);
int year = bytesToInt(byteArray[11], byteArray[12]);
int month = bytesToInt(byteArray[13], byteArray[14]);
int day = bytesToInt(byteArray[15], byteArray[16]);
int hour = bytesToInt(byteArray[17], byteArray[18]);
int minutes = bytesToInt(byteArray[19], byteArray[20]);
int second = bytesToInt(byteArray[21], byteArray[22]);
if (errorCode == 0x70) // 板故障
{
int slotId = positionId;
log.info("气体灭火 控制器:{} 信息类型:{} 板号:{} 设备类型: {} 上报错误码: {}, 时间:{}年 {}月 {}日 {}时 {}分 {}秒",
controlId, infoType, slotId, deviceType, errorCode, year, month, day, hour, minutes, second);
}
else
log.info("气体灭火 控制器:{} 信息类型:{} 部位:{} 设备类型: {} 上报错误码: {}, 时间:{}年 {}月 {}日 {}时 {}分 {}秒",
controlId, infoType, positionId, deviceType, errorCode, year, month, day, hour, minutes, second);
break;
}
case 0xFB: // 防火门
case 0xFC: // 电气火灾
case 0xFD: // 消防主机
{
int controlId = bytesToInt(byteArray[3], byteArray[4]);
int lineId = bytesToInt(byteArray[5], byteArray[6]);
int positionId = bytesToInt(byteArray[7], byteArray[8]);
int deviceType = bytesToInt(byteArray[9], byteArray[10]);
int year = bytesToInt(byteArray[11], byteArray[12]);
int month = bytesToInt(byteArray[13], byteArray[14]);
int day = bytesToInt(byteArray[15], byteArray[16]);
int hour = bytesToInt(byteArray[17], byteArray[18]);
int minutes = bytesToInt(byteArray[19], byteArray[20]);
int second = bytesToInt(byteArray[21], byteArray[22]);

int doorType = deviceType & 0xF;
int doorStatue = deviceType >> 4; // 高4位
if (errorCode == 0xFB)
log.info("防火门 控制器:{} 回路:{} 部位:{} 门类型:{} 门状态:{} 上报错误码: {}, 时间:{}年 {}月 {}日 {}时 {}分 {}秒",
controlId, lineId, positionId, doorType, doorStatue, errorCode, year, month, day, hour, minutes, second);
else if (errorCode == 0xFC)
log.info("电器火灾 控制器:{} 回路:{} 部位:{} 电气火灾类型:{} 电气火灾状态:{} 上报错误码: {}, 时间:{}年 {}月 {}日 {}时 {}分 {}秒",
controlId, lineId, positionId, doorType, doorStatue, errorCode, year, month, day, hour, minutes, second);
else
log.info("消防电源 控制器:{} 回路:{} 部位:{} 探测器类型:{} 探测器状态:{} 上报错误码: {}, 时间:{}年 {}月 {}日 {}时 {}分 {}秒",
controlId, lineId, positionId, doorType, doorStatue, errorCode, year, month, day, hour, minutes, second);
break;
}
default:
log.info("none alarm info: {}", readDataHex);
}
break;

default: // useless info
log.info("none alarm info: {}", readDataHex);
break;
}
}
*/
public void close() {
try {
in.close();
out.close();
serialPort.notifyOnDataAvailable(false);
serialPort.removeEventListener();
serialPort.close();
} catch (Exception e) {
log.error("close",e);
}
}



//16进制转byte数组
public static byte[] hexStrToByteArray(String str) {
if (str == null) {
return null;
}
if (str.length() == 0) {
return new byte[0];
}
byte[] byteArray = new byte[str.length() / 2];
for (int i = 0; i < byteArray.length; i++) {
String subStr = str.substring(2 * i, 2 * i + 2);
byteArray[i] = ((byte) Integer.parseInt(subStr, 16));
}
return byteArray;
}

public static String ByteArrayToString(byte[] by) {
String str = "";
for (int i = 0; i < by.length; i++) {
String hex = Integer.toHexString(by[i] & 0xFF);
if (hex.length() == 1) {
hex = "0" + hex;
}
str += hex.toUpperCase();
}
return str;
}

/**
* 把串口的byte转换成整数,最大0xFFFF,只转前2个byte
* @param
* @return int
*/
public static final int bytesToInt(byte bArrayH, byte bArrayL) {
int value = (bArrayH - 0x30) * 16 + ((byte)bArrayL - 0x30);
return value;
}

/**
* 数组转换成十六进制字符串
* @param
* @return HexString
*/
public static final String bytesToHexString(byte[] bArray) {
StringBuffer sb = new StringBuffer(bArray.length);
String sTemp;
for (int i = 0; i < bArray.length; i++) {
sTemp = Integer.toHexString(0xFF & bArray[i]);
if (sTemp.length() < 2)
sb.append(0);
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}

/**
* 获取源数据和验证码的组合byte数组
* @param aa 字节数组
* @return
*/
public static byte[] getData(byte[] aa) {
// byte[] bb = getCrc16(aa);
byte[] cc = new byte[aa.length];
System.arraycopy(aa,0,cc,0,aa.length);
return cc;
}

public static byte[] getData(String...strings) {
byte[] data = new byte[]{};
for (int i = 0; i<strings.length;i++) {
int x = Integer.parseInt(strings[i], 16);
byte n = (byte)x;
byte[] buffer = new byte[data.length+1];
byte[] aa = {n};
System.arraycopy( data,0,buffer,0,data.length);
System.arraycopy( aa,0,buffer,data.length,aa.length);
data = buffer;
}
return getData(data);
}

public static String byteTo16String(byte b) {
StringBuffer buffer = new StringBuffer();
int aa = (int)b;
if (aa<0) {
buffer.append(Integer.toString(aa+256, 16)+" ");
}else if (aa==0) {
buffer.append("00 ");
}else if (aa>0 && aa<=15) {
buffer.append("0"+Integer.toString(aa, 16)+" ");
}else if (aa>15) {
buffer.append(Integer.toString(aa, 16)+" ");
}
return buffer.toString();
}
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值