Opentcs 夜光开发手册(七)

博客主要讨论通讯相关内容,涉及客户端和服务端的编写。服务端用于虚拟测试,客户端实际使用,与AGV对接时可注释服务端代码。客户端和服务端由几个Class串连,实际测试只需编写客户端,重点是与PLC的编解码问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

夜光序言:

找伴侣,要看的永远是心,一颗善良的心,一颗上进的心,一颗充满责任感的心。远远比金钱更加重要。陪伴优质股成长,我一向认为是最完美的。

不在于未来能够获得多少,在于享受优质股前进的过程。

上图男主挺不错的,虽难人生比较坎坷,最后也收获了幸福

 

 

正文:这一篇聊一聊通讯~~,涉及客户端和服务端的编写,主要服务端是虚拟测试用,客户端则是实际用,实际与AGV对接时候,可以将服务端代码注释掉~~么么哒

package nettybak.netty.client.plc;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import nettybak.entity.PlcCmd;
import nettybak.netty.AgvConnectionUtils;
import nettybak.netty.AgvConstants;
import nettybak.util.HexUtils;
import nettybak.util.PlcCmdFactory;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;


/**  
 * @Title: PlcClient.java
 * @Package
 * @Description: plc通信客户端
 * @author    Genius  Team
 * @date
 */
public class PlcClient {
	private Logger logger=LoggerFactory.getLogger(getClass());

    private NioEventLoopGroup workGroup = new NioEventLoopGroup();
    private Channel channel;
    private Bootstrap bootstrap;
    private final String host;
    private final int port;
    private boolean sync=false;//是否同步等待连接创建完成,默认异步

    public PlcClient(){
    	this(AgvConstants.PLC_DEFAULT_HOST,AgvConstants.PLC_DEFAULT_PORT,false);
    }
    public PlcClient(String host, int port,boolean sync){
        this.host = host;
        this.port = port;
        this.sync=sync;
    }



    public void start() {
        try {
            bootstrap = new Bootstrap();
            bootstrap
                    .group(workGroup)
                    .channel(NioSocketChannel.class)
                    .handler(AgvConnectionUtils.plcClientInitializer);
            if(sync) {
            	ChannelFuture future=bootstrap.connect(host, port).sync();
            	channel=future.channel();
            	addReconnection(future);
            }else {
            	doConnect();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected void doConnect() {
        if (channel != null && channel.isActive()) {
        	logger.debug("夜光:当前链接有活动的channel,不执行重连逻辑");
            return ;
        }

        ChannelFuture future = bootstrap.connect(host, port);

        addReconnection(future);
        
    }

    public void addReconnection(ChannelFuture future) {
        future.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture futureListener) throws Exception {
                if (futureListener.isSuccess()) {
                    channel = futureListener.channel();
                } else {
                    logger.debug("链接失败"+AgvConstants.DEFAULT_REDIRECT_SECOND+"s后执行重连!");

                    futureListener.channel().eventLoop().schedule(new Runnable() {
                        @Override
                        public void run() {
                            doConnect();
                        }
                    }, AgvConstants.DEFAULT_REDIRECT_SECOND, TimeUnit.SECONDS);
                }
            }
        });   	
    }




	/**
	 * 本地非集成环境测试方法
	 * 夜光
	 * @throws Exception
	 */
	public void test() throws Exception {

		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

		while (true) {
			String readLine = in.readLine();
			PlcCmd msg=null;
			switch(readLine) {
			case "1" : msg = PlcCmdFactory.getUp(0); break;
			case "2" : msg = PlcCmdFactory.getDown(0); break;
			case "0" : msg = PlcCmdFactory.getStopJack(0); break;
			case "55" : msg = PlcCmdFactory.getInquiryStatus(); break;
			case "11" : msg = PlcCmdFactory.getForward(); break;
			case "12" : msg = PlcCmdFactory.getBackWards(); break;
			case "13" : msg = PlcCmdFactory.getToLeft(); break;
			case "14" : msg = PlcCmdFactory.getToRight(); break;
			case "15" : msg = PlcCmdFactory.getLevorotation(); break;
			case "16" : msg = PlcCmdFactory.getDextrorotation(); break;
			case "00" : msg = PlcCmdFactory.getStopRun(); break;
			}
			
			if("loop".equals(readLine)) {
				while(true) {
					msg=PlcCmdFactory.getUp(0);
					logger.debug("我是客户端,我准备发送的消息为-升顶:" + HexUtils.toHexString(msg.toByteArray()) );
					channel.writeAndFlush(msg);
					Thread.sleep(1000);
				}
			}
			if("loopd".equals(readLine)) {
				msg=PlcCmdFactory.getStopJack(0);
				logger.debug("我是客户端,我准备发送的消息为-停止顶升功能:" + HexUtils.toHexString(msg.toByteArray()) );
				channel.writeAndFlush(msg);
				while(true) {
					msg=PlcCmdFactory.getDown(0);
					logger.debug("我是客户端,我准备发送的消息为-落顶:" + HexUtils.toHexString(msg.toByteArray()) );
					channel.writeAndFlush(msg);
					Thread.sleep(1000);
				}
			}
			if("loopr".equals(readLine)) {
				while(true) {
					msg=PlcCmdFactory.getInquiryStatus();
					logger.debug("我是客户端,我准备发送的消息为-问询:" + HexUtils.toHexString(msg.toByteArray()) );
					channel.writeAndFlush(msg);
					Thread.sleep(1000);
				}
			}
			
			logger.debug("我是客户端,我准备发送的消息为:" + HexUtils.toHexString(msg.toByteArray()) );
			channel.writeAndFlush(msg);
		}

	}
	
	
    public void writeAndFlush(PlcCmd msg) {
    	logger.debug("我是客户端,我准备发送的消息为:" + HexUtils.toHexString(msg.toByteArray()) );
    	channel.writeAndFlush(msg);
    }
    
    public static void main(String[] args) throws Exception {
    	PlcClient client = new PlcClient(AgvConstants.PLC_DEFAULT_HOST,AgvConstants.PLC_DEFAULT_PORT,true);
        client.start();
        client.test();
    }

    
	
}









 

package nettybak.netty.client.plc;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import nettybak.entity.PlcResult;
import nettybak.util.CRC16;
import nettybak.util.HexUtils;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.ByteProcessor.IndexOfProcessor;



/**  
 * @Package
 * @Description: plc通信解码器-解码Plc反馈的信息
 * @author Genius  Team
 * @date
 */
public class PlcClientDecoder extends ByteToMessageDecoder {

	private static final Logger logger=LoggerFactory.getLogger(PlcClientDecoder.class);
	
	/**
	 * 解码PLC返回的数据.
	 * <p>该方法执行结束,如果没有新的可读数据,即ridx==widx,则会释放该字节缓冲区资源
	 * @param ctx
	 * @param in
	 * @param out
	 * @throws Exception
	 * @see io.netty.handler.codec.ByteToMessageDecoder#decode(io.netty.channel.ChannelHandlerContext, io.netty.buffer.ByteBuf, List)
	 */
	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
	
		
		//-----TEST START
		in.markReaderIndex();
        int len=in.readableBytes();
        byte[] read=new byte[len];
        in.readBytes(read);
        logger.trace("decode解码PLC反馈的数据,接受到的数据字节长度为: {} ,值为:{}",len,HexUtils.toHexString(read));    
        in.resetReaderIndex();
		//-----TEST END
        
        
        
		//夜光:按照 站号和功能码 组合做头部分帧
		
		int numIdx=in.forEachByte(new IndexOfProcessor((byte) 0x01));//在当前可读字节中寻找"站号"第一次出现的位置[数据过滤,认为站号之前的未读数据为垃圾数据]
		if(numIdx==-1) {
			return;//没有找到则return继续等待新内容写入
		}
		in.readerIndex(numIdx);//将当前读索引设置为站号所在位置
		if (in.readableBytes() < PlcResult.BASE_LEN) {
			 return;//以当前站号为起始,可读数据小于头部固定长度(站号+功能码+字节总数),则return继续等待写入
		 }
		byte typeCode=in.getByte(in.readerIndex()+1);//以不推进readerIndex的方式获取到当前指令对应的功能码.(功能码固定位于指令开头的索引+1处)
		//如果在对应位置读取不到正确的功能码,则认为当前位置不是正确的头部位置,以推进readIndex的方式,丢弃该位置和该位置以前的数据
		if(typeCode!=0x03) {
			in.readerIndex(in.readerIndex()+1+1);//in.readerIndex()+1只包括该位置以前的数据,所以要再加一次1,把该位置的也丢弃
			return;
		}
		
		byte dataLength=in.getByte(in.readerIndex()+2);//以不推进readerIndex的方式获取到当前指令对应的字节总数.(字节总数固定位于指令开头的索引+2处)
		if(in.readableBytes()<PlcResult.BASE_LEN+dataLength+PlcResult.CHECK_LEN) {
			return;//以当前站号为起始,可读数据不小于头部固定长度(站号+功能码+字节总数),但可读数据小于当前指令的字节总数,则return继续等待写入
		}
		
		//所有条件都符合,尝试读取封装数据
		 PlcResult result= packPlcResult(in);
		 //如果result不为空,则调用out.add(result); 数据会进入信道的下一个处理器,
		 if(result!=null) {
			 out.add(result);
		 }

	}
	
	/**
	 * 将plc反馈的字节数据封装为对应的实体对象
	 * <p>如果封装完成后的对象,校验和与计算出的校验和不匹配,则认为数据在传输中出现错误,则返回null
	 * @param in
	 * @return
	 */
	public PlcResult packPlcResult(ByteBuf in) {
		PlcResult plcResult=new PlcResult();
		plcResult.setCode(in.readByte());
		plcResult.setTypeCode(in.readByte());
		plcResult.setDataLength(in.readByte());
		byte[] datas=new byte[plcResult.getDataLength()];
		in.readBytes(datas);
		plcResult.setData(datas);
		plcResult.setChecksum(in.readShort());
		short ck=(short)CRC16.calcCrc16(plcResult.toNoCheckByteArray());
		if(ck!=0&&ck==plcResult.getChecksum()) {
			return plcResult;
		}else {
			return null;
		}
	}

}
package nettybak.netty.client.plc;

import nettybak.entity.PlcCmd;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;



/**  
 * @Package
 * @Description: plc通信编码器-编码发送给PLC的指令
 * @author  Genius  Team
 * @date
 */
public class PlcClientEncoder extends MessageToByteEncoder<PlcCmd>  {

	@Override
	protected void encode(ChannelHandlerContext ctx, PlcCmd cmd, ByteBuf out) throws Exception {
		byte[] bytes=cmd.toByteArray();
		out.writeBytes(bytes);
	}

}

 

package nettybak.netty.client.plc;

import nettybak.netty.AgvConstants;
import nettybak.netty.client.BaseChannelInitializer;
import nettybak.netty.client.BaseClientHandler;

import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleStateHandler;


/**  
 * @Title: PlcClientInitializer.java
 * @Package
 * @Description: plc通信处理器初始化
 * @author    Genius   Team
 * @date
 */
public class PlcClientInitializer extends BaseChannelInitializer<SocketChannel> {
	
	public PlcClientInitializer(BaseClientHandler<?> handler) {
		super(handler);
	}

		@Override
	    public void initChannel(SocketChannel ch) throws Exception {
	        ChannelPipeline pipeline = ch.pipeline();
	        
	       // pipeline.addLast(new IdleStateHandler(AgvConstants.DEFAULT_HEARTBEAT_SECOND, 0, 0));//心跳
	        pipeline.addLast("decoder", new PlcClientDecoder());
	        pipeline.addLast("encoder", new PlcClientEncoder());
//	        pipeline.addLast("handler", this.getBusinessHandler());
	    }
	}

 

 

一般客户端和服务端都是差不多几个Class串在一起嗯~~

 

package nettybak.netty.client.plc;

import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import nettybak.entity.AgvEntity;
import nettybak.entity.AgvSession;
import nettybak.entity.PlcCmd;
import nettybak.entity.PlcCommand;
import nettybak.entity.PlcResult;
import nettybak.netty.AgvUtils;
import nettybak.util.HexUtils;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;


/**  
 * @Title: PlcDefaultClientHandler.java
 * @Package
 * @Description: plc通信默认业务处理器
 * @author   Genius  Team
 * @date
 */
@Sharable
public class PlcDefaultClientHandler extends PlcBaseClientHandler {
	
	private Logger logger=LoggerFactory.getLogger(getClass());



	
	/**
	 * 处理下发plc指令
	 * @param ctx
	 * @param s
//	 * @see 
	 */
	@Override
	protected void businessHandler(ChannelHandlerContext ctx, PlcResult s) {
		//接收到消息后触发onReadMsg事件
////		AgvEntity entity=new AgvEntity();
//		entity.setPlcResult(s);
//		onReadMsg(entity);
		
		
		Channel channel=ctx.channel();
		//目前channelKey为远程主机的ip,根据具体情况,channelKey可以在消息体中携带(除外,不支持)
		 InetSocketAddress insocket = (InetSocketAddress) channel.remoteAddress();
         String remoteIP = insocket.getAddress().getHostAddress();
         //logger.debug("我是客户端,当前链接的IP为:"+remoteIP);
         //System.out.println("SYSOUT-我是客户端,我接收到的消息为:"+s);
         logger.debug("我是plc客户端,当前链接的plc-IP为:{}我接收到的消息为:{}",remoteIP,HexUtils.toHexString(s.toByteArray()));
	
	}




	
	/**
	 * 
	 * 
	 * 测试方法-链接建立成功后发送一条指令
	 * @param ctx
	 * @throws Exception
//	 * @see netty.client.plc.PlcBaseClientHandler#channelActive(io.netty.channel.ChannelHandlerContext)
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		// TODO Auto-generated method stub
		super.channelActive(ctx);
		PlcCmd cmd=new PlcCmd();
		//ctx.writeAndFlush(cmd);
	}
	
	

}

 

 

package nettybak.netty.client.plc;

import java.net.InetSocketAddress;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import nettybak.entity.PlcResult;
import nettybak.netty.AgvConnectionUtils;
import nettybak.netty.client.BaseClientHandler;
import nettybak.util.Q600CmdFactory;

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleStateEvent;

/**  
 * @Title: PlcBaseClientHandler.java
 * @Package
 * @Description: plc通信业务处理器基类
 * @author   Genius  Team
 * @date
 */
public abstract class PlcBaseClientHandler extends  BaseClientHandler<PlcResult> {
	
	
	private Logger logger=LoggerFactory.getLogger(getClass());
	
	
	
	/**
	 * 嗯唔:需要实现的处理业务逻辑的方法
	 */
	protected abstract void businessHandler(ChannelHandlerContext ctx, PlcResult s);
	

	
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, PlcResult s) throws Exception {
		businessHandler(ctx, s);
	}
    
    /**
     * 心跳处理
     * @param ctx
     * @param evt
     * @throws Exception
     * @see io.netty.channel.ChannelInboundHandlerAdapter#userEventTriggered(io.netty.channel.ChannelHandlerContext, Object)
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // IdleStateHandler 所产生的 IdleStateEvent 的处理逻辑.
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;
            switch (e.state()) {
                case READER_IDLE:
                    handleReaderIdle(ctx);
                    break;
                case WRITER_IDLE:
                    handleWriterIdle(ctx);
                    break;
                case ALL_IDLE:
                    handleAllIdle(ctx);
                    break;
                default:
                    break;
            }
        }
    }
    
    /**
     * 信道被激活(每当成功建立链接时,信道会被激活)
     * @param ctx
     * @throws Exception
     * @see io.netty.channel.ChannelInboundHandlerAdapter#channelActive(io.netty.channel.ChannelHandlerContext)
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
       logger.debug("---" + ctx.channel().remoteAddress() + " 信道被激活---");
       
    }

    
    /**
     * 断开自动重连 
     * @param ctx
     * @throws Exception
     * @see io.netty.channel.ChannelInboundHandlerAdapter#channelInactive(io.netty.channel.ChannelHandlerContext)
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    	super.channelInactive(ctx);
    	logger.debug("---" + ctx.channel().remoteAddress() + " is inactive---连接中断开,执行重连逻辑");
        InetSocketAddress addr=(InetSocketAddress)ctx.channel().remoteAddress();
        String ip=addr.getAddress().getHostAddress();
        int port=addr.getPort();
        AgvConnectionUtils.connectPlc(ip, port, false);
    }

    /**处理指定的时间间隔内没有读到数据
     * @param ctx
     */
    protected void handleReaderIdle(ChannelHandlerContext ctx) {
       logger.debug("---READER_IDLE---指定的时间间隔内没有读到agv返回的数据,准备发送 问询坐标 指令");
       //到了指定的间隔时间,就问询一次坐标
       ctx.channel().writeAndFlush(Q600CmdFactory.Q600_GET_POSE);
       logger.debug("---READER_IDLE--发送问询坐标指令完成");
    }

    /**处理指定的时间间隔内没有写入数据
     * @param ctx
     */
    protected void handleWriterIdle(ChannelHandlerContext ctx) {
       logger.debug("---WRITER_IDLE---指定的时间间隔内没有写入数据");
    }

    /**处理指定的时间间隔内没有读/写入数据
     * @param ctx
     */
    protected void handleAllIdle(ChannelHandlerContext ctx) {
       logger.debug("---ALL_IDLE---指定的时间间隔内没有读/写入数据");
    }
}

 

 

总结:实际测试只要编写上面客户端就可以了嗯,主要就是与PLC之间的编解码问题

 

 

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值