How to realize 485 communication through Java

Posted by fipp on Fri, 29 Oct 2021 04:34:47 +0200

1. Selection of overall architecture

First of all, according to the requirements, I use the springboot+netty architecture, using a serial port to network port conversion module. Why use it like this? The Linux system is used during deployment. It is troublesome to install the driver under Linux, so the network port can save a lot of server configuration time. Why use netty? Many people who have used netty know that netty is an asynchronous non blocking framework. You can check its specific advantages. It is a very powerful framework. The conversion module uses someone's module. It's OK to buy it on Taobao. It has also used modules from other manufacturers, such as yibaite. It's not easy to use. Someone's module is quite mature.

2. What is modbus

When it comes to 485 communication, we will think of Modbus protocol. Modbus protocol is a general language applied to electronic controller. There is no more explanation here. Here we use the Modbus RTU protocol. A separate article will be written later.

3. Code parsing

After we have a certain understanding of this architecture, we can start coding. First, we need to define the server and client. If our program is set as the server, the module should be set as the client. The final effect is the same. I prefer to set the program as the server.

3.1 server monitoring

The listener listens for client connections

import com.nari.sea.serialport.config.ServerChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;

@Component
public class NettyServer {
    private static final Logger logger  = Logger.getLogger(NettyServer.class);
    public void start(InetSocketAddress address) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup(4);
        try {
            ServerBootstrap bootstrap = new ServerBootstrap()
                    .group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(address)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ServerChannelInitializer())
                    .option(ChannelOption.SO_BACKLOG, 128)
                    //Open long connection
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            // Bind the port and start receiving incoming connections
             ChannelFuture future = bootstrap.bind(address).sync();
             logger.info("Server start listen at " + address.getPort());
             future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

3.2 netty heartbeat mechanism

All of them can be configured according to their needs. netty determines by default that the client is offline for two hours. Through the following code, we can set it to seconds and minutes. The following four parameters are read, write, full and time types respectively.

channel.pipeline().addLast(new IdleStateHandler(1, 0, 0, TimeUnit.MINUTES));
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;

import java.util.concurrent.TimeUnit;

public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        channel.pipeline().addLast("decoder",new StringDecoder(CharsetUtil.ISO_8859_1));
        channel.pipeline().addLast("encoder",new StringEncoder(CharsetUtil.ISO_8859_1));
        //Set n minutes to judge offline
        channel.pipeline().addLast(new IdleStateHandler(1, 0, 0, TimeUnit.MINUTES));
        //Sticking length control
        channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4));
        channel.pipeline().addLast(new ServerHandler());
    }
}

3.3 specific implementation

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import java.util.List;


public class ServerHandler extends ChannelInboundHandlerAdapter {
    //Timeout connection
    private int lossConnectCount = 0;

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("channelActive----->");
    }

    @Override
      public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent){
            IdleStateEvent event = (IdleStateEvent)evt;
            //Judge only read
            if (event.state()== IdleState.READER_IDLE){
                lossConnectCount++;
                if (lossConnectCount>5){
                    System.out.println("Turn off inactive channels!");
                    ctx.channel().close();
                }
                System.out.println("already"+lossConnectCount+"I haven't received a message from the client for minutes!");
            }
        }else {
            super.userEventTriggered(ctx,evt);
        }
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception{
        //Client exit connection
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        lossConnectCount = 0;
        System.out.println("server channelRead......");
        String m= (String) msg;
        //Judgment communication
        ByteBuf buf = Unpooled.copiedBuffer(m, CharsetUtil.ISO_8859_1);
        byte [] bytes = new byte[buf.readableBytes()];
        buf.readBytes(bytes);//Copy contents to byte array bytes
        String returnData = bytes2HexString(bytes); //Convert byte array to hexadecimal string
        System.out.println(ctx.channel().remoteAddress()+"----->Server :"+ returnData);
                if(returnData != null && !"".equals(returnData)){
                    if(HexUtil.checkData(returnData)){
                    //Specific logic code

                    }
                }


        //The information of the client is directly returned and written to ctx
        //Refresh cache
    }

    private String bytes2HexString(byte[] b) {
        StringBuffer result = new StringBuffer();
        String hex;
        for (int i = 0; i < b.length; i++) {
            hex = Integer.toHexString(b[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            result.append(hex.toUpperCase());
        }
        return result.toString();
    }
    private String hexString2String(String hex, String charset) {
        byte[] bs = new byte[hex.length()/2];
        for(int i=0; i<bs.length; i++) {
            bs[i] = (byte)(0xff&Integer.parseInt(hex.substring(i*2,i*2+2),16));
        }
        try{
            hex = new String(bs, charset);
        }catch(Exception e) {
            e.printStackTrace();
        }
        return hex;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

3.4 how to introduce service into netty

In the logic implementation code, we inevitably need to introduce some service s. At this time, we need to do so in the implementation code

@Component
public class SerialPortDealData {
    @Autowired
    private ModeManService modeManService;

    //Declaration object
    private static SerialPortDealData serialPortDealData;

    // initialization
    @PostConstruct
    public void init() {
        serialPortDealData = this;
        serialPortDealData.modeManService = this.modeManService;
    }

    //Logic code
}

3.5 how to start netty

There are many ways to start netty in spring boot, one of which is introduced here

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

import java.net.InetSocketAddress;

@EnableScheduling
@SpringBootApplication
@ServletComponentScan//Prevent @ WebListener from becoming invalid
@MapperScan(basePackages ={""})
@ComponentScan(basePackages = {""})
@EnableAsync//Note here that this annotation enables thread pooling
public class SeaApplication extends SpringBootServletInitializer implements CommandLineRunner{

    @Value("${netty.port}")
    private int port;

    @Value("${netty.url}")
    private String url;

    @Autowired
    private NettyServer server;


    public static void main(String[] args) {
        SpringApplication.run(SeaApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(SeaApplication.class);
    }

    @Override
    public void run(String... args) throws Exception {
        InetSocketAddress address = new InetSocketAddress(url,port);
        System.out.println("run  .... . ... "+url);
        server.start(address);
    }

}

Application.yml configuration file

netty:
  port: 5001
  url: 127.0.0.1

4. Thanks

So far, a method of realizing 485 communication has been introduced. Thank you for reading. Please guide me if there are any deficiencies. Finally, let's talk about some simulation tools, modbus poll - corresponding modbus slave, modbus scan, network debugging tool NetAssist.exe, serial port debugging tool, etc

Topics: Java Netty Spring Boot Back-end modbus