JAVA版DLT645解析
前言
几年前一个项目需要用到,研究过一段时间,现在也忘得差不多了,所以主要贴代码为主,想到哪些说哪些,见谅;
开发参照DLT645-2007多功能电能表通信协议(2015)
代码结构
一个简单得netty框架加上645协议解析,
大致说明
DTU设置好IP和端口波特率,再现场通过232或者485连接仪表,然后dtu和服务端软件建立tcp连接;
所以我们做的就只是起一个netty服务监听一个端口,等dtu设备和咱们建立tcp连接
首先是netty的配置
@Slf4j
public class DTL645DiscardServer implements Runnable{
private final int port;
public DTL645DiscardServer(int port) {
this.port = port;
}
@Override
public void run() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup(100);
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new DTL645ChannelInitializer());
ChannelFuture f = b.bind(port);
log.info("==== server start ====");
// Channel channel = b.bind(port).sync().channel();
// channel.closeFuture().sync();
f.channel().closeFuture().sync();
log.info("==== server end ====");
} catch (Exception e){
log.error("启动数据采集监听出错 ",e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
// new CommandFetcherTest().run();
new DTL645DiscardServer(61026).run();
}
}
服务启动监听端口
@Component
@Slf4j
public class ApplicationRunnerImpl implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("启动监听端口61026");
DTL645DiscardServer discardServer = new DTL645DiscardServer(61026);
Thread thread = new Thread(discardServer);
thread.start();
}
}
model目录
这个目录下定义有构建dlt645发送报文和解析报文的两个对象,还有两个全局map
ChannelMap ( 设备信息:通道号)
CtxMap (通道号:通道对象)
ReadData2007 (解析报文)
SendData2007 (发送报文)
后者是根据报文结构构建好对象方便使用,关于dlt645数据帧结构可以看看网上文章了解,这里没别人写的好,不做讲解了;
前两个是处理注册后的连接,这里注册就是首次会把设备id发过来,设备id带上固定得校验,ok得放入map里,不ok就给他关掉
ChannelMap :
public class ChannelMap {
private static ConcurrentHashMap<String, Object> MAP = null;
public ChannelMap() {
}
public static ConcurrentHashMap<String, Object> getMap() {
if (MAP == null){
synchronized (CtxMap.class){
if(MAP == null){
MAP = new ConcurrentHashMap<String, Object>();
}
}
}
return MAP;
}
}
CtxMap :
public class CtxMap {
private static ConcurrentHashMap<String, ChannelHandlerContext> MAP = null;
public CtxMap() {
}
public static ConcurrentHashMap<String, ChannelHandlerContext> getMap() {
if (MAP == null){
synchronized (CtxMap.class){
if(MAP == null){
MAP = new ConcurrentHashMap<String, ChannelHandlerContext>();
}
}
}
return MAP;
}
}
ReadData2007
public class ReadData2007 {
private int[] effectiveData; //有效数据
private int controlCode; //控制码
private Integer dot; //确定小数点
private String deviceAddress; //表地址
private byte[] dataType; //数据标识
public ReadData2007(byte[] bytes) {
/**
* 起始符 地址域 起始符 控制码 数据域长度 数据域 校验码 结束符
* 0 1~6 7 8 9 9+n
* 1字节 6字节 1字节 1字节 1字节 n字节(4字节数据标识+数据) 1字节 1字节
*/
this.deviceAddress = CheckUtil.GetBCDAddress(bytes);
boolean check = CheckUtil.checkData(bytes);
if (!check) {
System.out.println("check error");
System.out.print("error msg: ");
for (byte b : bytes) {
System.out.print(b + " ");
}
return;
} else {
int[] read_ints = ConvertUtil.bytesToInts(bytes);
this.controlCode = read_ints[8]; //控制码
int lengthOfData = read_ints[9]; //数据域长度
if (read_ints.length>12&&lengthOfData==1){//对于更改速率的应答,数据域中无标识符,只有一个字节;从站异常应答也是如此
this.effectiveData = new int[]{read_ints[10]};
}
if (read_ints.length>16&&lengthOfData>=4){//在有数据域的情况下
byte[] type = new byte[4]; // 4字节的数据标识
int[] data = new int[lengthOfData - 4]; //除去两字节的数据标识剩下的数据长度
for (int t = 0; t < 4; t++) {
type[t] = (byte) (read_ints[10 + t] - 0x33); //构建数据标识 DI0 DI1
}
if (lengthOfData>4){
for (int d = 0; d < lengthOfData - 4; d++) {
data[d] = read_ints[14 + d] - 0x33; //获取真正数据域
// 2007
//data[d] = Integer.parseInt(ConvertUtil.intToHex(data[d]));//会报异常:NumberFormatException.forInputString
}
}
this.effectiveData = data;
this.dataType = type;
DataIdentify2007 dataIdentify2007 = new DataIdentify2007();
this.dot = dataIdentify2007.doc.get(Arrays.toString(type));
}
}
}
public int[] getEffectiveData() {
return effectiveData;
}
public void setEffectiveData(int[] effectiveData) {
this.effectiveData = effectiveData;
}
public int getControlCode() {
return controlCode;
}
public void setControlCode(int controlCode) {
this.controlCode = controlCode;
}
public Integer getDot() {
return dot;
}
public void setDot(Integer dot) {
this.dot = dot;
}
public String getDeviceAddress() {
return deviceAddress;
}
public void setDeviceAddress(String deviceAddress) {
this.deviceAddress = deviceAddress;
}
public byte[] getDataType() {
return dataType;
}
public void setDataType(byte[] dataType) {
this.dataType = dataType;
}
@Override
public String toString() {
return "ReadData1997{" +
"effectiveData=" + Arrays.toString(effectiveData) +
", controlCode=" + controlCode +
", dot=" + dot +
", deviceAddress='" + deviceAddress + '\'' +
", dataType=" + Arrays.toString(dataType) +
'}';
}
}
public class SendData2007 {
private int[] address; //地址域
private int control; //控制码
private int length; //数据长度
private int[] data; //数据,包括数据标识
int cs; //校验码
public SendData2007(int[] address, int control, int[] data){
this.address = address;
this.control = control;
this.data = data;
this.length=data.length;
}
//构建完整的帧,发送前调用
public byte[] send() {
int[] temp = new int[12+data.length];
temp[0] = Constants.START;//帧起始符
temp[7] = temp[0];//帧结束符
for (int i = 1; i <= address.length; i++) {//地址域
temp[i] = address[i-1];
}
temp[8] = control;//控制码
temp[9] = length;//数据长度
if (data.length>0){
for (int i = 0; i <data.length ; i++) {//数据
temp[10+i] = data[i] + 0x33;
}
}
//得到校验码
cs =0;
for(int i=0;i<temp.length-2;i++) {
cs += (temp[i] & 0xff) % 256;
}
temp[temp.length-2] = cs; //校验码
temp[temp.length-1]= Constants.ENDING;//结束符
byte[] msg = new byte[temp.length];
//转化为字节
for (int i = 0; i < temp.length; i++) {
msg[i] = (byte) temp[i];
}
return msg;
}
@Override
public String toString() {
return "SendData1997{" +
"address=" + Arrays.toString(address) +
", control=" + control +
", length=" + length +
", data=" + Arrays.toString(data) +
", cs=" + cs +
'}';
}
}
nio
nio目录下是一个通用netty框架运用
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new IdleStateHandler(600,0,0));
// ch.pipeline().addLast(new StringEncoder());
// ch.pipeline().addLast(new DTLHandler());
// ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new Encoder());
ch.pipeline().addLast(new DTL645DiscardServerHandler());
}
主要贴一下DTL645ChannelInitializer 和 DTL645Analysis
大致是DTL645DiscardServerHandler处理报文数据,排除心跳帧后调用DTL645Analysis做数据得处理,具体见注释
DTL645DiscardServerHandler:
@Slf4j
public class DTL645DiscardServerHandler extends SimpleChannelInboundHandler<Object> {
private String status = "init";
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
int length = byteBuf.readableBytes();
byte[] tmps = new byte[length];
byteBuf.readBytes(tmps);
String s = bytesToHexString(tmps);
log.info("数据接收:"+s);
// fe 为心跳包
if (tmps.length != 1){
DTL645Analysis dtl645Analysis = new DTL645Analysis(tmps,ctx);
Thread thread = new Thread(dtl645Analysis);
thread.start();
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
log.info("连接断开");
// 通道号:通道对象
ConcurrentHashMap<String,ChannelHandlerContext> ctxMap = CtxMap.getMap();
// 设备信息:通道号
ConcurrentHashMap<String,Object> channelMap = ChannelMap.getMap();
if (ctxMap.contains(ctx)){
ctxMap.remove(ctx);
channelMap.remove(ctx.channel().id().toString());
System.out.println("剔除已经断开通道");
}
}
public String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder("");
if ((src == null) || (src.length <= 0)) {
return null;
}
for (byte aSrc : src) {
int v = aSrc & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv).append(" ");
}
return stringBuilder.toString();
}
}
DTL645Analysis :
public class DTL645Analysis implements Runnable{
private byte[] sourcesBytes;
private ChannelHandlerContext ctx;
public DTL645Analysis(byte[] sourcesBytes, ChannelHandlerContext ctx) {
this.sourcesBytes = sourcesBytes;
this.ctx = ctx;
}
@Override
public void run() {
sourcesBytes = transferred(transferred(sourcesBytes, 0xed), 0xee); //转义
// 通道号:通道对象
ConcurrentHashMap<String,ChannelHandlerContext> ctxMap = CtxMap.getMap();
// 设备信息:通道号
ConcurrentHashMap<String,Object> channelMap = ChannelMap.getMap();
// Rtu设备编号
// 第一次接入
if (channelMap.get(ctx.channel().id().toString()) == null){
String rtuName = new String(conByte(sourcesBytes, 4, sourcesBytes.length - 7));
System.out.println("rtuName"+rtuName);
// 注册
if (rtuName.contains("87775236")){
System.out.println("首次注册"+rtuName);
channelMap.put(ctx.channel().id().toString(), rtuName);
ctxMap.put(rtuName, ctx);
// 测试一下发送
// SendHelper.readData(ctx,"810000218497", CommandType.A_PHASE_VOLTAGE);
}else {
ctx.close();
}
}else {
int num = 0;
for (int i = 0; i < sourcesBytes.length; i++) {
if (sourcesBytes[i] == 104){
num = i;
break;
}
}
// 发送的命令 反馈的数据
byte[] newSourcesBytes = new byte[sourcesBytes.length-num];
arraycopy(sourcesBytes, num, newSourcesBytes, 0, sourcesBytes.length-num);
System.out.println(bytesToHexString(newSourcesBytes));
System.out.println("处理前"+bytesToHexString(sourcesBytes));
System.out.println("处理后"+bytesToHexString(newSourcesBytes));
ReadData2007 readData2007 = new ReadData2007(newSourcesBytes);
System.out.println(readData2007.toString());
int controlCode = readData2007.getControlCode();
switch (controlCode){
case 145: // 0x91 主站请求读数据应答后 -- 无后续数据帧情况
System.out.println("数据:"+getDataResult(readData2007));
break;
case 177: // 0xb1 有后续数据帧
System.out.println("有后续数据帧");
break;
case 209:
System.out.println("异常");
break;
default:
System.out.println("返回数据出错");
break;
}
}
}
private double getDataResult(ReadData2007 readData) {
int[] data = readData.getEffectiveData();
StringBuilder sb = new StringBuilder();
for (int i = data.length - 1; i >= 0; i--) {
sb.append(Integer.toHexString(data[i]).length()<2?"0"+Integer.toHexString(data[i]):Integer.toHexString(data[i]));
}
sb.insert(sb.length() - readData.getDot(), ".");
return Double.valueOf(sb.toString());
}
private static byte[] transferred(byte[] paramsBytes, int params) { //字节转义
byte[] tmpEd = new byte[paramsBytes.length];
int countFd = 0;
for (int i = 0; i < paramsBytes.length; i++) {
if ((paramsBytes[i] & 0xff) == 0xfd) { //检查数据里面是否包含特殊字节 0xfd
if (i + 1 < paramsBytes.length) {
if ((paramsBytes[i + 1] & 0xff) == params) {
switch (params) {
case 0xed:
tmpEd[i - countFd] = (byte) 0xfd;
break;
case 0xee:
tmpEd[i - countFd] = (byte) 0xfe;
break;
}
i++;
countFd++;
} else {
tmpEd[i - countFd] = paramsBytes[i];
}
} else {
tmpEd[i - countFd] = paramsBytes[i];
}
} else { //不是特殊字节 0xfd
tmpEd[i - countFd] = paramsBytes[i];
}
}
byte[] resultBytes = conByte(tmpEd, 0, tmpEd.length - 1 - countFd);
return resultBytes;
}
private static byte[] conByte(byte[] tmp, int start, int end) {
byte[] b = new byte[end - start + 1];
int j = 0;
for (int i = start; i <= end; i++) {
b[j] = tmp[i];
j++;
}
return b;
}
public String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder("");
if ((src == null) || (src.length <= 0)) {
return null;
}
for (byte aSrc : src) {
int v = aSrc & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv).append(" ");
}
return stringBuilder.toString();
}
}
其他一些重要得配置说明
DataIdentify2007:等同于电能量标志编码表,定义了数据标志,格式,长度,单位等等,我这里好像只用到了数据格式,也就是xxxx.xx 确定小数点在哪里,其他的没写那么细,在params也定义了些,需求上简单,不需要干弄那么多
public class DataIdentify2007 {
public List<byte[]> dataIdent = new ArrayList<>();
public Map<String,String> identifyname=new HashMap<String,String>();
public Map<String,Integer> doc=new HashMap<String,Integer>();
public int length;
public DataIdentify2007() {
dataIdent.add(0,new byte[]{(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00});//有功总电能
dataIdent.add(1,new byte[]{(byte)0x00,(byte)0x00,(byte)0x01,(byte)0x00});//正向有功总电能
dataIdent.add(2,new byte[]{(byte)0x00,(byte)0x00,(byte)0x02,(byte)0x00});//反向有功总电能
dataIdent.add(3,new byte[]{(byte)0x00,(byte)0x01,(byte)0x01,(byte)0x02});//A相电压
dataIdent.add(4,new byte[]{(byte)0x00,(byte)0x02,(byte)0x01,(byte)0x02});//B相电压
dataIdent.add(5,new byte[]{(byte)0x00,(byte)0x03,(byte)0x01,(byte)0x02});//C相电压
dataIdent.add(6,new byte[]{(byte)0x00,(byte)0x01,(byte)0x02,(byte)0x02});//A相电流
dataIdent.add(7,new byte[]{(byte)0x00,(byte)0x02,(byte)0x02,(byte)0x02});//B相电流
dataIdent.add(8,new byte[]{(byte)0x00,(byte)0x03,(byte)0x02,(byte)0x02});//C相电流
dataIdent.add(9,new byte[]{(byte)0x00,(byte)0x00,(byte)0x03,(byte)0x02});//瞬时有功功率
dataIdent.add(10,new byte[]{(byte)0x00,(byte)0x01,(byte)0x03,(byte)0x02});//瞬时A相有功功率
dataIdent.add(11,new byte[]{(byte)0x00,(byte)0x02,(byte)0x03,(byte)0x02});//瞬时B相有功功率
dataIdent.add(12,new byte[]{(byte)0x00,(byte)0x03,(byte)0x03,(byte)0x02});//瞬时C相有功功率
dataIdent.add(13,new byte[]{(byte)0x00,(byte)0x00,(byte)0x04,(byte)0x02});//瞬时无功功率
dataIdent.add(14,new byte[]{(byte)0x00,(byte)0x01,(byte)0x04,(byte)0x02});//瞬时A相总无功功率
dataIdent.add(15,new byte[]{(byte)0x00,(byte)0x02,(byte)0x04,(byte)0x02});//瞬时B相总无功功率
dataIdent.add(16,new byte[]{(byte)0x00,(byte)0x03,(byte)0x04,(byte)0x02});//瞬时C相总无功功率
dataIdent.add(17,new byte[]{(byte)0x00,(byte)0x00,(byte)0x05,(byte)0x02});//瞬时视在功率
dataIdent.add(18,new byte[]{(byte)0x00,(byte)0x01,(byte)0x05,(byte)0x02});//A相视在功率
dataIdent.add(19,new byte[]{(byte)0x00,(byte)0x02,(byte)0x05,(byte)0x02});//B相视在功率
dataIdent.add(20,new byte[]{(byte)0x00,(byte)0x03,(byte)0x05,(byte)0x02});//C相视在功率
dataIdent.add(21,new byte[]{(byte)0x00,(byte)0x00,(byte)0x06,(byte)0x02});//总功率因数
dataIdent.add(22,new byte[]{(byte)0x00,(byte)0x01,(byte)0x06,(byte)0x02});//A相功率因数
dataIdent.add(23,new byte[]{(byte)0x00,(byte)0x02,(byte)0x06,(byte)0x02});//B相功率因数
dataIdent.add(24,new byte[]{(byte)0x00,(byte)0x03,(byte)0x06,(byte)0x02});//C相功率因数
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00}),"total_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x01,(byte)0x00}),"pos_positive_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x02,(byte)0x00}),"neg_positive_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x01,(byte)0x02}),"a_voltage");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x02,(byte)0x01,(byte)0x02}),"b_voltage");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x03,(byte)0x01,(byte)0x02}),"c_voltage");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x02,(byte)0x02}),"a_current");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x02,(byte)0x02,(byte)0x02}),"b_current");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x03,(byte)0x02,(byte)0x02}),"c_current");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x03,(byte)0x02}),"positive_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x03,(byte)0x02}),"a_positive_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x02,(byte)0x03,(byte)0x02}),"b_positive_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x03,(byte)0x03,(byte)0x02}),"c_positive_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x04,(byte)0x02}),"reactive_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x04,(byte)0x02}),"a_reactive_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x02,(byte)0x04,(byte)0x02}),"b_reactive_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x03,(byte)0x04,(byte)0x02}),"c_reactive_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x05,(byte)0x02}),"apparent_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x05,(byte)0x02}),"a_apparent_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x02,(byte)0x05,(byte)0x02}),"b_apparent_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x03,(byte)0x05,(byte)0x02}),"c_apparent_power");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x06,(byte)0x02}),"influence");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x06,(byte)0x02}),"a_influence");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x02,(byte)0x06,(byte)0x02}),"b_influence");
identifyname.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x03,(byte)0x06,(byte)0x02}),"c_influence");
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x01,(byte)0x00}),2); // 当前正向有功费率1电能
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x00,(byte)0x00}),2);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x01,(byte)0x00}),2);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x02,(byte)0x00}),2);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x01,(byte)0x02}),1);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x02,(byte)0x01,(byte)0x02}),1);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x03,(byte)0x01,(byte)0x02}),1);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x02,(byte)0x02}),3);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x02,(byte)0x02,(byte)0x02}),3);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x03,(byte)0x02,(byte)0x02}),3);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x03,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x03,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x02,(byte)0x03,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x03,(byte)0x03,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x04,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x04,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x02,(byte)0x04,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x03,(byte)0x04,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x05,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x05,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x02,(byte)0x05,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x03,(byte)0x05,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x00,(byte)0x06,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x01,(byte)0x06,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x02,(byte)0x06,(byte)0x02}),4);
doc.put(Arrays.toString(new byte[]{(byte)0x00,(byte)0x03,(byte)0x06,(byte)0x02}),4);
}
public int getLength()
{
return dataIdent.size();
}
}
CommandType:
public enum CommandType {
NONE(""),//无命令
//读数据的命令,统一以0开头
CUR_POSITIVE_ACTIVE_POWER("04000402"),//读表号 --phase voltage positive active power
A_PHASE_VOLTAGE("02010100"),
Z_POSITIVE_ACTIVE_POWER("00010100"), // 正向有功总电能
Z_POSITIVE_ACTIVE_POWER1("00010000") // 正向有功总电能
;
private String value;
CommandType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
/**
* 提取器 此处获取地址和连接 向从机请求数据
* @Author: yele
* @Date: 2021/1/14 14:43
*/
@Slf4j
@Component
public class CommandFetcher {
@Scheduled(cron = "*/15 * * * * ?")
public void send(){
System.out.println("定时任务发送");
// 通道号:通道对象
ConcurrentHashMap<String, ChannelHandlerContext> ctxMap = CtxMap.getMap();
if (ctxMap.size() > 0){
System.out.println("发送测试");
ChannelHandlerContext ctx = ctxMap.get("02887775236");
ChannelHandlerContext ctx1 = ctxMap.get("02787775236");
ChannelHandlerContext ctx2 = ctxMap.get("03087775236");
// SendHelper.readData(ctx,"810000218497", CommandType.Z_POSITIVE_ACTIVE_POWER);
// SendHelper.readData(ctx,"000000111111", CommandType.Z_POSITIVE_ACTIVE_POWER);
// SendHelper.readData(ctx,"810000218497", CommandType.A_PHASE_VOLTAGE);
// String strShunshi = "FE FE FE FE 68 97 84 21 00 00 81 68 11 04 33 34 34 33 6E 16"; //读取瞬时流量
// String[] strShunshiArray = strShunshi.split(" ");
// byte[] tmpShunshi = new byte[strShunshiArray.length];
// for (int i = 0; i < tmpShunshi.length; i++) {
// tmpShunshi[i] = (byte) Integer.parseInt(strShunshiArray[i], 16);
// }
// ctx.writeAndFlush(tmpShunshi);//发送读取数据命令
if (ctx1 != null){
SendHelper.readData(ctx1,"810000218495", CommandType.Z_POSITIVE_ACTIVE_POWER1);
}
//
// if (ctx2 != null){
// SendHelper.readData(ctx2,"000002062701", CommandType.Z_POSITIVE_ACTIVE_POWER1);
// }
// String strShunshi1 = "68 97 84 21 00 00 81 68 11 04 33 34 34 33 70 16"; //读取瞬时流量
// String[] strShunshiArray1 = strShunshi1.split(" ");
// byte[] tmpShunshi1 = new byte[strShunshiArray1.length];
// for (int i = 0; i < tmpShunshi1.length; i++) {
// tmpShunshi1[i] = (byte) Integer.parseInt(strShunshiArray1[i], 16);
// }
// ctx.writeAndFlush(tmpShunshi1);//发送读取数据命令
//
// System.out.println("发送"+strShunshi1);
}
}
}
总结
写的比较乱,代码也没贴全,后面我把代码也放上来,这里再次说明下处理的思路;
首先设备注册进来,长连接就建立了,两个全局的map存储的就是设备对应的通道,设备不会主动发数据过来,需要做一个请求读的操作。举个例子获取设备id为02787775236的正向有功总电能
ChannelHandlerContext ctx1 = ctxMap.get("02787775236");
if (ctx1 != null){
SendHelper.readData(ctx1,"810000218495", CommandType.Z_POSITIVE_ACTIVE_POWER);
}
获得应答处理
int controlCode = readData2007.getControlCode();
switch (controlCode){
case 145: // 0x91 主站请求读数据应答后 -- 无后续数据帧情况
System.out.println("数据:"+getDataResult(readData2007));
break;
case 177: // 0xb1 有后续数据帧
System.out.println("有后续数据帧");
break;
case 209:
System.out.println("异常");
break;
default:
System.out.println("返回数据出错");
break;
}
https://download.csdn.net/download/Gyele/87689835