详细
3、编写程序server端加上特殊字符解码器
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
//分隔符
ByteBuf delimiter = Unpooled.copiedBuffer("##".getBytes());
//特殊字符结束符包解析
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(ParamManage.MAXLENGTH,
false,
true,
delimiter));
ch.pipeline().addLast(new ServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true);
处理handler
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String strReq = Util.bytesToHexString(req);
log.info("收到的请求是:" + strReq);
if(StrUtil.isNotEmpty(strReq)){
log.info(AnalysisUtil.getSendTime(req));
log.info(AnalysisUtil.getSourceAddress(req));
log.info(AnalysisUtil.getDestinationAddress(req));
int unitLength = (req[24] & 0xff) + ((req[25] & 0xff) << 8);
log.info("unitLength = " + unitLength);
int command = req[26] & 0xff;
log.info("command = " + command);
int messageLength = 30;
if (unitLength > 0 && req.length == messageLength + unitLength){
servicesHandle(req);
}
if (req.length != messageLength + unitLength){
byte[] result = new ResponseBean().fail(req);
log.info("MESSAGE LENGTH ERROR,FAIL MESSAGE:" + Util.bytesToHexString(result));
ByteBuf echo = Unpooled.copiedBuffer(result);
ctx.writeAndFlush(echo);
}else{
byte[] result = new ResponseBean().success(req);
log.info("RIGHT,RESPOND:" + Util.bytesToHexString(result));
ByteBuf echo = Unpooled.copiedBuffer(result);
ctx.writeAndFlush(echo);
}
}else{
ByteBuf echo= Unpooled.copiedBuffer(new ResponseBean().wrongFormat());
ctx.writeAndFlush(echo);
}
super.channelRead(ctx, msg);
}
统一返回bean
public class ResponseBean {
private static final String SUCCESS = "SUCCESS";
private static final String FAIL = "FAIL";
public byte[] success(byte[] bytes){
return handle(bytes,SUCCESS);
}
public byte[] wrongFormat(){
return handle(null,null);
}
public byte[] fail(byte[] bytes){
return handle(bytes,FAIL);
}
private byte[] handle(byte[] bytes ,String command){
Calendar c = Calendar.getInstance();
int second = c.get(Calendar.SECOND) & 0xff;
int minute = c.get(Calendar.MINUTE) & 0xff;
int hour = c.get(Calendar.HOUR_OF_DAY) & 0xff;
int day = c.get(Calendar.DATE) & 0xff;
int month = (c.get(Calendar.MONTH) + 1) & 0xff;
int year = Integer.parseInt(String.valueOf(c.get(Calendar.YEAR)).substring(2)) & 0xff;
if(SUCCESS.equals(command) || FAIL.equals(command)){
byte[] comBytes = new byte[] { bytes[2], bytes[3], (byte)0x01, (byte)0x01,
(byte)second, (byte)minute, (byte)hour,(byte)day,(byte)month,(byte)year,
bytes[18], bytes[19], bytes[20],bytes[21],bytes[22],bytes[23],
bytes[12], bytes[13], bytes[14],bytes[15],bytes[16],bytes[17],(byte)0x00, (byte)0x00, SUCCESS.equals(command) ? (byte)0x03 : (byte)0x06};
byte crc = calCRC(comBytes);
byte[] result = new byte[]{(byte)0x40, (byte)0x40,
bytes[2], bytes[3], (byte)0x01, (byte)0x01,
(byte)second, (byte)minute, (byte)hour,(byte)day,(byte)month,(byte)year,
bytes[18], bytes[19], bytes[20],bytes[21],bytes[22],bytes[23],
bytes[12], bytes[13], bytes[14],bytes[15],bytes[16],bytes[17],(byte)0x00, (byte)0x00, SUCCESS.equals(command) ? (byte)0x03 : (byte)0x06,
crc,(byte)0x23, (byte)0x23};
return result;
}else{
byte[] comBytes = new byte[] { (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x01,
(byte)second, (byte)minute, (byte)hour,(byte)day,(byte)month,(byte)year,
(byte)0x00, (byte)0x00, (byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00, (byte)0x00,(byte)0x06};
byte crc = calCRC(comBytes);
byte[] result = new byte[]{(byte)0x40, (byte)0x40,
(byte)0x00, (byte)0x00, (byte)0x01, (byte)0x01,
(byte)second, (byte)minute, (byte)hour,(byte)day,(byte)month,(byte)year,
(byte)0x00, (byte)0x00, (byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00, (byte)0x00,(byte)0x06,
crc,(byte)0x23, (byte)0x23};
return result;
}
}
private byte calCRC(byte[] data){
int sum = 0, len = data.length;
for (int i = 0; i < len; i++){
sum += data[i];
}
byte crc = (byte)(sum & 0xff);
return crc;
}
}
| 应用数据单元类型标志定义表 | ||
| 类型值 | 说明 | 方向 |
| 0 | 预留 | 上行 |
| 1 | 上传建筑消防设施系统状态 | |
| 2 | 上传建筑消防设施部件运行状态 | |
| 3 | 上传建筑消防设施部件模拟量值 | |
| 4 | 上传建筑消防设施操作信息 | |
| 5 | 上传建筑消防设施软件版本 | |
| 6 | 上传建筑消防设施系统配置情况 | |
| 7 | 上传建筑消防设施部件配置情况 | |
| 8 | 上传建筑消防设施系统时间 | |
| 9~20 | 预留(建筑消防设施信息) | |
| 21 | 上传用户信息传输装置运行状态 | |
| 22 | 预留 | |
| 23 | 预留 | |
| 24 | 上传用户信息传输装置操作信息 | |
| 25 | 上传用户信息传输装置软件版本 | |
| 26 | 上传用户信息传输装置配置情况 | |
| 27 | 预留 | |
| 28 | 上传用户信息传输装置系统时间 | |
| 29~40 | 预留(用户信息传输装置信息) | |
| 41~60 | 预留(控制信息) | |
| 61 | 读建筑消防设施系统状态 | 下行 |
| 62 | 读建筑消防设施部件运行状态 | |
| 63 | 读建筑消防设施部件模拟量值 | |
| 64 | 读建筑消防设施操作信息 | |
| 65 | 读建筑消防设施软件版本 | |
| 66 | 读建筑消防设施系统配置情况 | |
| 67 | 读建筑消防设施部件配置情况 | |
| 68 | 读建筑消防设施系统时间 | |
| 69~80 | 顶留 | |
| 81 | 读用户信息传输装置运行状态 | |
| 82 | 预留 | |
| 83 | 预留 | |
| 84 | 读用户信息传输装置操作信息记录 | |
| 85 | 读用户信息传输装置软件版本 | |
| 86 | 读用户信息传输装置配置情况 | |
| 87 | 预留 | |
| 88 | 读用户信息传输装置系统时间 | |
| 89 | 初始化用户信息传输装置 | |
| 90 | 同步用户信息传输装置时钟 | |
| 91 | 查岗命令 | |
| 92~127 | 预留 | |
| 128~254 | 用户自定义 | |
| 128 | 上传用户信息传输装置生产日期 | 上行 |
| 129 | 上传用户报名信息 | |
| 130 | 上传用户信息传输装置开机时间信息 | |
| 131 | 上传用户信息传输装置关机时间信息 | |
| 132 | 上传建筑消防设施系统开机信息 | |
| 133 | 上传建筑消防设施系统关机信息 | |
| 134 | 上传建筑消防设施系统状态恢复 | |
| 135 | 上传建筑消防设施部件运行状态恢复 | |
| 136 | 上传用户信息传输装置运行状态恢复 | |
| 137 | 上传建筑消防设施部件运行状态(自定义) | |
| 138 | 上传建筑消防设施部件运行状态(自定义)恢复 | |
| 139 | 上传用户信息传输装置用户身份识别信息 | |
| 140 | 上传建筑消防设施系统状态(自定义) | |
| 141 | 上传建筑消防设施系统状态(自定义)恢复 | |
| 188 | 读用户信息传输装置生产日期 | 下行 |
| 189 | 设置用户信息传输装置报名时间 | |
| 197 | 读建筑消防设施部件运行状态(自定义) | |
demo目前处理2种类型
case 2 处理方法
public static void faultAlarm(byte[] bytes, int nums){
for (int i = 0; i < nums; i++) {
//系统类型
int sysType = bytes[29 + i * 46] & 0xff;
//系统地址
int sysAddress = bytes[30 + i * 46] & 0xff;
//部件地址
int parts = bytes[31 + i * 46] & 0xff;
log.info("sysType:"+sysType+" sysAddress:"+sysAddress+" parts:"+parts);
int mill = (bytes[69+ i * 46] & 0xff);
int minute = (bytes[70+ i * 46] & 0xff);
int hour = (bytes[71+ i * 46] & 0xff);
int day = (bytes[72+ i * 46] & 0xff);
int month = (bytes[73+ i * 46] & 0xff);
int year = (bytes[74+ i * 46] & 0xff);
Date createTime = null;
try {
createTime = new SimpleDateFormat( "yy-MM-dd HH:mm:ss" ).parse(year+"-"+month+"-"+day+ " " +
hour + ":"+ minute + ":" + mill);
} catch (ParseException e) {
e.printStackTrace();
}
// 上传部件地址与消防系统设备地址的对应关系
String deviceAddress = AddressMapping.getDeviceAddress(bytes[32 + i * 46], bytes[33 + i * 46], bytes[34 + i * 46], bytes[35 + i * 46]);
//火警
int data1 = bytes[36+ i * 46] >>> 1 & 0x1;
//故障
int data2 = bytes[36+ i * 46] >>> 2 & 0x1;
if(data1 == 1){
log.warn("火警"+deviceAddress+" "+createTime);
}
if(data2 == 1){
log.warn("故障"+deviceAddress+" "+createTime);
}
}
}
地址对应关系可以查看文档
附上地址对应处理方法。部分type 试过 ,常用为0,1,2,3
public static String getDeviceAddress(byte lowBit, byte highBit, byte lowArea, byte highArea){
int bitCode;
bitCode = (lowBit & 0xff);
bitCode += (highBit & 0xff) << 8;
int areaCode;
areaCode = (lowArea & 0xff);
areaCode += (highArea & 0xff) << 8;
log.info("bitCode:"+bitCode+" areaCode:"+areaCode);
String tempAreaCode = String.format("%05d", areaCode);
String arrCode="";
switch (TYPE){
case 0 :
//回路号 -设备号
arrCode = areaCode + "-" + bitCode;
break;
case 1 :
//主机号2 -回路号3 -设备号
arrCode = Integer.parseInt(tempAreaCode.substring(0,2)) + "-" + Integer.parseInt(tempAreaCode.substring(2)) + "-" + String.format("%03d", bitCode);
break;
case 2 :
//主机号3 -回路号2 -设备号
arrCode = Integer.parseInt(tempAreaCode.substring(0,3)) + "-" + Integer.parseInt(tempAreaCode.substring(3)) + "-" + String.format("%03d", bitCode);
break;
case 3 :
arrCode += highArea & 0xff;
arrCode += lowArea & 0xff;
arrCode += highBit & 0xff;
arrCode += lowBit & 0xff;
break;
case 4 :
//栋/区/层/设备号
arrCode += ((highArea & 0xff) +"/");
arrCode += ((lowArea & 0xff) +"/");
arrCode += ((highBit & 0xff) +"/");
arrCode += ((lowBit & 0xff) +"/");
break;
default:
break;
}
return arrCode;
}
4、测试报文
------无数据包 4040 @@ 0000 流水号 01 主版本号 01 协议版本号 07 7秒 1D 29分 0A 10时 1B 27日 04 4月 16 22年 F60100000000 源地址 FE7900000000 目标地址 0000 应用数据长度 02 命令 D5 校验和 2323 ## ------手报 4040 0100 01 01 30 30s 2A 42 0D 13h 07 05 16 F60100000000 FE7900000000 0A 00 10L 02 1801040A002A0D070516 数据长度 18 为24是系统类型标志( 上传用户信息传输装置操作信息) 002A0D070516 时间 86 2323 ------火警 4040 0500 01 01 16 2A 0E 11 05 16 410100000000 2A3000000000 3000 48L 02 02010101007100E903020020202020202020202020202020202020202020202020202020202020202000002A0E110516 D7 2323 02 01 01 系统类型 01 系统地址 00 部件类型 7100 位号(设备号) 113 01110001 00000000 E903 区号( )1001 11101001 00000011 0200 数据 20202020202020202020202020202020202020202020202020202020202000 部件说明 002A0E110516 时间祝各位成功



