Modbus通讯协议(四)——Java实现ModbusTCP Slave(从机)

本文通过Java开源库jlibmodbus实现modbusTCP通讯 从机端

添加依赖

<dependency>
    <groupId>com.intelligt.modbus</groupId>
    <artifactId>jlibmodbus</artifactId>
    <version>1.2.9.7</version>
</dependency>

Java实现从机读写操作

ModbusSlaveUtil 类,主程序创建Modbus Slave监听事件

import com.intelligt.modbus.jlibmodbus.Modbus;
import com.intelligt.modbus.jlibmodbus.data.ModbusHoldingRegisters;
import com.intelligt.modbus.jlibmodbus.exception.IllegalDataAddressException;
import com.intelligt.modbus.jlibmodbus.exception.IllegalDataValueException;
import com.intelligt.modbus.jlibmodbus.exception.ModbusIOException;
import com.intelligt.modbus.jlibmodbus.slave.ModbusSlave;
import com.intelligt.modbus.jlibmodbus.slave.ModbusSlaveFactory;
import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters;
import io.github.talelin.latticy.model.TPSensorValueDO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;

//springboot启动时创建tcp slave
@Component
public class ModbusSlaveUtil
        implements CommandLineRunner
{

    @Override
    public void run(String... args) throws Exception {
        Thread.sleep(10000);
        new Thread(new Runnable() {

            @Override
            public void run() {
                createSalve();
            }
        }).start();
    }

    private static int[] arr = new int[]{0,0};

    public static ModbusSlave slave ;

    public void createSalve(){
        try{
            initSlave();
            System.out.println("slave start...........");
            createDataHolder();
            closeSlave();
        }catch (Exception e){

        }
    }

    public static void initSlave() throws UnknownHostException, ModbusIOException {
        TcpParameters tcpParameters = new TcpParameters();// 设置从机TCP参数
        InetAddress adress = InetAddress.getByName("127.0.0.1");// 设置TCP的ip地址
        // getLocalHost()返回的是本机地址
        // tcpParameters.setHost(InetAddress.getLocalHost());
        tcpParameters.setHost(adress); // 为从机TCP设置上述ip地址参数
        // 设置从机TCP的是否长连接,通俗点讲就是一直保持连接,一次连接完下次就不要在连接了
        tcpParameters.setKeepAlive(true);
        tcpParameters.setPort(Modbus.TCP_PORT);// 设置从机TCP的端口
        // 创建一个从机
        slave = ModbusSlaveFactory.createModbusSlaveTCP(tcpParameters);
        // 设置控制台输出主机和从机命令交互日志
        Modbus.setLogLevel(Modbus.LogLevel.LEVEL_DEBUG);

    }


    public void createDataHolder() throws ModbusIOException {
        // 创建从机的寄存器
        MyOwnDataHolder dh = new MyOwnDataHolder();

        // 为从机寄存器添加监听事件,这里的监听事件主要是主机如果发送写命令修改从机则控制台输出
        dh.addEventListener(new ModbusEventListener() {
            @Override
            public int[] readHoldingRegisterRange(int offset, int quantity){
                System.out.println("readHoldingRegisterRange: 读取信息 offset = " + offset+";quantity"+quantity);
                try{
                    updateHoldingRegisters(offset, quantity);
                }catch (Exception e){
                    e.printStackTrace();
                }
                return arr;
            }

            @Override
            public int[] readInputRegisterRange(int offset, int quantity){
                System.out.println("readInputRegisterRange: 读取信息 offset = " + offset+";quantity"+quantity);
                try{
                    updateInputRegisters(offset, quantity);
                }catch (Exception e){
                    e.printStackTrace();
                }
                return arr;
            }

			@Override
			public void onWriteToSingleCoil(int address, boolean value) {
				System.out
						.print("onWriteToSingleCoil: address " + address + ", value " + value);
			}
 
			@Override
			public void onWriteToMultipleCoils(int address, int quantity, boolean[] values) {
				System.out.print("onWriteToMultipleCoils: address " + address + ", quantity "
						+ quantity);
			}
 
			@Override
			public void onWriteToSingleHoldingRegister(int address, int value) {
				System.out.print("onWriteToSingleHoldingRegister: address " + address
						+ ", value " + value);
			}

			@Override
			public void onWriteToMultipleHoldingRegisters(int address, int quantity,
					int[] values) {
				System.out.print("onWriteToMultipleHoldingRegisters: address " + address
						+ ", quantity " + quantity);
			}
				
        });
        // 为从机设置寄存器
        slave.setDataHolder(dh);
        // 设置从机的读超时时间,建议主机读的超时时间小于该值
        slave.setReadTimeout(1500);
        // 为从机设置从机服务地址slaveid
        slave.setServerAddress(1);
        // 开启从机监听事件,必须要这一句
        slave.listen();
    }

    public static void closeSlave() throws InterruptedException, ModbusIOException {
        //这部分代码主要是设置Java虚拟机关闭的时候需要做的事情,即本程序关闭的时候需要做的事情,直接使用即可
        if (slave.isListening()) {
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                synchronized (slave) {
                    slave.notifyAll();
                }
            }));

            synchronized (slave) {
                slave.wait();
            }
            slave.shutdown();
            System.out.println("slave shutdown........");
        }
    }

    public void updateHoldingRegisters(int offset, int quantity) throws IllegalDataAddressException, IllegalDataValueException {
        List<Float> list =  Arrays.asList(10.1f,20.3f,89.5f,77.353f);
        ModbusHoldingRegisters hr = new ModbusHoldingRegisters(10000);
        // 修改数值寄存器对应位置的值,第一个参数代表寄存器地址,第二个代表修改的数值
        //hr.set有几个方法,根据自己要赋值的数据类型选择,此处示例的是赋值float类型,一个float是4个字节,32bit,对应2个寄存器所以i*2
        for(int i=0;i<list.size();i++){
            hr.setFloat32At(i*2, list.get(i));
        }
        slave.getDataHolder().setHoldingRegisters(hr);
    }

    public void updateInputRegisters(int offset, int quantity) throws IllegalDataAddressException, IllegalDataValueException, ModbusIOException {
        List<Float> list =  Arrays.asList(10.1f,20.3f,89.5f,77.353f);
        ModbusHoldingRegisters hr = new ModbusHoldingRegisters(10000);
        // 修改数值寄存器对应位置的值,第一个参数代表寄存器地址,第二个代表修改的数值
        for(int i=0;i<list.size();i++){
            hr.setFloat32At(i*2, list.get(i));
        }
        slave.getDataHolder().setInputRegisters(hr);
    }



}


ModbusEventListener 接口

public interface ModbusEventListener {
    int[] readHoldingRegisterRange(int offset, int quantity);

    int[] readInputRegisterRange(int offset, int quantity);

    void onWriteToSingleCoil(int address, boolean value);

    void onWriteToMultipleCoils(int address, int quantity, boolean[] values);

    void onWriteToSingleHoldingRegister(int address, int value);

    void onWriteToMultipleHoldingRegisters(int address, int quantity, int[] values);

}

MyOwnDataHolder 继承jlibmodbus类库中的DataHolder

import com.intelligt.modbus.jlibmodbus.data.DataHolder;
import com.intelligt.modbus.jlibmodbus.exception.IllegalDataAddressException;

import java.util.ArrayList;
import java.util.List;

public class MyOwnDataHolder extends DataHolder {

    final List<ModbusEventListener> modbusEventListenerList = new ArrayList<ModbusEventListener>();

    public MyOwnDataHolder() {
        // you can place the initialization code here
        /*
         * something like that: setHoldingRegisters(new
         * SimpleHoldingRegisters(10)); setCoils(new Coils(128)); ... etc.
         */
    }

    public void addEventListener(ModbusEventListener listener) {
        modbusEventListenerList.add(listener);
    }

    public boolean removeEventListener(ModbusEventListener listener) {
        return modbusEventListenerList.remove(listener);
    }

    @Override
    public int[] readHoldingRegisterRange(int offset, int quantity) throws IllegalDataAddressException {
        for (ModbusEventListener l : modbusEventListenerList) {
            l.readHoldingRegisterRange(offset, quantity);
        }
        return super.readHoldingRegisterRange(offset, quantity);
    }

    @Override
    public int[] readInputRegisterRange(int offset, int quantity) throws IllegalDataAddressException{
        for (ModbusEventListener l : modbusEventListenerList) {
            l.readInputRegisterRange(offset, quantity);
        }
        return super.readInputRegisterRange(offset, quantity);
    }

	@Override
	public void writeHoldingRegister(int offset, int value) throws IllegalDataAddressException,
			IllegalDataValueException {
		for (ModbusEventListener l : modbusEventListenerList) {
			l.onWriteToSingleHoldingRegister(offset, value);
		}
		super.writeHoldingRegister(offset, value);
	}

	@Override
	public void writeHoldingRegisterRange(int offset, int[] range)
			throws IllegalDataAddressException, IllegalDataValueException {
		for (ModbusEventListener l : modbusEventListenerList) {
			l.onWriteToMultipleHoldingRegisters(offset, range.length, range);
		}
		super.writeHoldingRegisterRange(offset, range);
	}

	@Override
	public void writeCoil(int offset, boolean value) throws IllegalDataAddressException,
			IllegalDataValueException {
		for (ModbusEventListener l : modbusEventListenerList) {
			l.onWriteToSingleCoil(offset, value);
		}
		super.writeCoil(offset, value);
	}

	@Override
	public void writeCoilRange(int offset, boolean[] range) throws IllegalDataAddressException,
			IllegalDataValueException {
		for (ModbusEventListener l : modbusEventListenerList) {
			l.onWriteToMultipleCoils(offset, range.length, range);
		}
		super.writeCoilRange(offset, range);
	}

}

测试

启动程序,用MODSCAN模拟modbus主机读取数据,本例从机发送的是float,使用MODSCAN测试时工具栏上的数据类型选浮点数就可以直接显示出正确的数据了在这里插入图片描述

参考文章 Java实现ModBus的slave端(从机server端,发送数据),代码略有修改,添加了监听读取保持寄存器和读取输入寄存器的方法,并在监听到读事件时给寄存器赋值。

还有在一开始run()的时候,增加一个线程去处理业务,进而不至于阻塞主线程的启动,也不会因为出错致使主线程挂掉。因为原来的代码是在主线程上开启监听slave,在开发工具中运行正常,部署到tomcat上运行起来访问不到url,连tomcat主页都显示不出来。这个问题可以参考这篇文章:Spring Boot中实现CommandLineRunner完成启动时加载数据在内外置tomcat条件下的启动差异及正确的使用姿势

  • 8
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三毛村滴雪鱼粉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值