|
@@ -0,0 +1,330 @@
|
|
|
+package com.jg.config;
|
|
|
+
|
|
|
+import com.alibaba.fastjson2.JSON;
|
|
|
+import com.alibaba.fastjson2.JSONArray;
|
|
|
+import com.intelligt.modbus.jlibmodbus.Modbus;
|
|
|
+import com.intelligt.modbus.jlibmodbus.data.ModbusCoils;
|
|
|
+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.net.stream.base.ModbusInputStream;
|
|
|
+import com.intelligt.modbus.jlibmodbus.slave.ModbusSlave;
|
|
|
+import com.intelligt.modbus.jlibmodbus.slave.ModbusSlaveFactory;
|
|
|
+import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters;
|
|
|
+import com.jg.service.ModBusEventListener;
|
|
|
+import com.jg.service.MyOwnDataHolder;
|
|
|
+import com.jg.util.WebSocketServer;
|
|
|
+import com.jg.vo.ShowVo;
|
|
|
+import com.jg.vo.TagVo;
|
|
|
+import jdk.nashorn.internal.ir.annotations.Reference;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.boot.CommandLineRunner;
|
|
|
+import org.springframework.boot.context.event.ApplicationReadyEvent;
|
|
|
+import org.springframework.context.Phased;
|
|
|
+import org.springframework.context.event.EventListener;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.net.InetAddress;
|
|
|
+import java.net.UnknownHostException;
|
|
|
+import java.nio.ByteBuffer;
|
|
|
+import java.nio.ByteOrder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.util.*;
|
|
|
+import java.util.concurrent.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * <p>
|
|
|
+ * springboot启动时创建tcp slave
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @author lxp
|
|
|
+ * @version V1.0
|
|
|
+ * @since 2023/6/2 15:26
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+public class ModBusSlaveServer implements CommandLineRunner {
|
|
|
+
|
|
|
+ @Reference
|
|
|
+ private WebSocketServer webSocketServer;
|
|
|
+
|
|
|
+ private String show;
|
|
|
+ private Map<Integer,ShowVo> showVoMap = new ConcurrentHashMap<>();
|
|
|
+
|
|
|
+ ExecutorService service = new ThreadPoolExecutor(
|
|
|
+ // corePoolSize线程池的核心线程数
|
|
|
+ 2,
|
|
|
+ // maximumPoolSize能容纳的最大线程数
|
|
|
+ 5,
|
|
|
+ // keepAliveTime空闲线程存活时间
|
|
|
+ 1L,
|
|
|
+ // unit 存活的时间单位
|
|
|
+ TimeUnit.SECONDS,
|
|
|
+ // workQueue 存放提交但未执行任务的队列
|
|
|
+ new LinkedBlockingQueue<>(3),
|
|
|
+ // threadFactory 创建线程的工厂类
|
|
|
+ Executors.defaultThreadFactory(),
|
|
|
+ // handler 等待队列满后的拒绝策略
|
|
|
+ new ThreadPoolExecutor.AbortPolicy()
|
|
|
+ );
|
|
|
+
|
|
|
+ @EventListener(ApplicationReadyEvent.class)
|
|
|
+ private void aa(){
|
|
|
+ InputStream inputStream = getClass().getClassLoader().getResourceAsStream("tag.json");
|
|
|
+ try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()){
|
|
|
+ byte[] buffer = new byte[1024];
|
|
|
+ int length;
|
|
|
+ while ((length = inputStream.read(buffer)) != -1) {
|
|
|
+ byteArrayOutputStream.write(buffer, 0, length);
|
|
|
+ }
|
|
|
+ show = byteArrayOutputStream.toString(StandardCharsets.UTF_8.name());
|
|
|
+ // 使用 show 字符串
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ List<TagVo> tagVos = JSON.parseArray(show, TagVo.class);
|
|
|
+ for (TagVo tagVo: tagVos){
|
|
|
+ ShowVo showVo = new ShowVo();
|
|
|
+ showVo.setName(tagVo.getDesc());
|
|
|
+ showVo.setUnit(tagVo.getUnit());
|
|
|
+ showVoMap.put(tagVo.getOffset(),showVo);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ @Autowired
|
|
|
+ private ModBusSlaveConfiguration configuration;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void run(String... args) throws Exception {
|
|
|
+ Thread.sleep(1000);
|
|
|
+ service.execute(this::createSalve);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final int[] ARR = new int[]{0, 0};
|
|
|
+
|
|
|
+ List<Float> list = Arrays.asList(10.1f, 20.3f, 89.5f, 77.353f);
|
|
|
+
|
|
|
+ public static ModbusSlave slave;
|
|
|
+
|
|
|
+ public void createSalve() {
|
|
|
+ try {
|
|
|
+ initSlave();
|
|
|
+ log.info("slave start...........");
|
|
|
+ createDataHolder();
|
|
|
+ closeSlave();
|
|
|
+ } catch (InterruptedException | ModbusIOException | UnknownHostException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化ModBus从机
|
|
|
+ *
|
|
|
+ * @throws UnknownHostException e
|
|
|
+ */
|
|
|
+ public void initSlave() throws UnknownHostException {
|
|
|
+ // 设置从机TCP参数
|
|
|
+ TcpParameters tcpParameters = new TcpParameters();
|
|
|
+ // 设置TCP的ip地址
|
|
|
+ InetAddress address = InetAddress.getByName(configuration.getModBusHost());
|
|
|
+ // 为从机TCP设置上述ip地址参数 getLocalHost()返回的是本机地址
|
|
|
+ tcpParameters.setHost(address);
|
|
|
+ // 设置从机TCP的是否长连接,通俗点讲就是一直保持连接,一次连接完下次就不要在连接了
|
|
|
+ tcpParameters.setKeepAlive(true);
|
|
|
+ // 设置从机TCP的端口
|
|
|
+ tcpParameters.setPort(configuration.getModBusPort());
|
|
|
+ // 创建一个从机
|
|
|
+ slave = ModbusSlaveFactory.createModbusSlaveTCP(tcpParameters);
|
|
|
+ // 设置控制台输出主机和从机命令交互日志
|
|
|
+ Modbus.setLogLevel(Modbus.LogLevel.LEVEL_RELEASE);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建从机的寄存器
|
|
|
+ *
|
|
|
+ * @throws ModbusIOException e
|
|
|
+ */
|
|
|
+ public void createDataHolder() throws ModbusIOException {
|
|
|
+ // 创建从机的寄存器
|
|
|
+ MyOwnDataHolder holder = new MyOwnDataHolder();
|
|
|
+ ModbusHoldingRegisters modbusHoldingRegisters = new ModbusHoldingRegisters(200);
|
|
|
+ holder.setHoldingRegisters(modbusHoldingRegisters);
|
|
|
+ ModbusCoils coils = new ModbusCoils(10);
|
|
|
+ holder.setCoils(coils);
|
|
|
+ ModbusHoldingRegisters inputRegisters = new ModbusHoldingRegisters(10);
|
|
|
+ holder.setInputRegisters(inputRegisters);
|
|
|
+
|
|
|
+ // 为从机寄存器添加监听事件,这里的监听事件主要是主机如果发送写命令修改从机则控制台输出
|
|
|
+ holder.addEventListener(new ModBusEventListener() {
|
|
|
+ @Override
|
|
|
+ public int[] readHoldingRegisterRange(int offset, int quantity) {
|
|
|
+ // System.out.println("readHoldingRegisterRange: 读取信息 offset = " + offset + ";quantity = " + quantity);
|
|
|
+// log.info("[readHoldingRegisterRange]configuration: {}", configuration);
|
|
|
+// try {
|
|
|
+// updateHoldingRegisters(offset, quantity);
|
|
|
+// } catch (Exception e) {
|
|
|
+// e.printStackTrace();
|
|
|
+// }
|
|
|
+ return ARR;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int[] readInputRegisterRange(int offset, int quantity) {
|
|
|
+// log.info("[readInputRegisterRange]configuration: {}", configuration);
|
|
|
+ // 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);
|
|
|
+ System.out.println(Arrays.toString(values));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onWriteToSingleHoldingRegister(int address, int value) {
|
|
|
+ System.out.print("onWriteToSingleHoldingRegister: address = " + address + ", value = " + value);
|
|
|
+ configuration.getInitValues().set(address, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onWriteToMultipleHoldingRegisters(int address, int quantity, int[] values) {
|
|
|
+ System.out.print("onWriteToMultipleHoldingRegisters: address = " + address + ", quantity = " + quantity);
|
|
|
+ System.out.println(Arrays.toString(values));
|
|
|
+ // List<TagVo> tagVos = new ArrayList<>();
|
|
|
+// Map<Integer, TagVo> result = new HashMap<>();
|
|
|
+
|
|
|
+ for (int i = 0; i < values.length; i += 2) {
|
|
|
+ Integer offset = address + i;
|
|
|
+// result.get(offset);
|
|
|
+ float resultBigEndian1 = convertRegistersToFloat(values[i], values[i+1], ByteOrder.BIG_ENDIAN,false);
|
|
|
+ showVoMap.get(offset).setValue(new BigDecimal(Float.toString(resultBigEndian1)));
|
|
|
+ System.out.println(resultBigEndian1);
|
|
|
+ }
|
|
|
+ JSONArray array = new JSONArray();
|
|
|
+ for (ShowVo showVo : showVoMap.values()) {
|
|
|
+ array.add(showVo);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ webSocketServer.sendInfo(array.toString(), "push");
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+// float resultBigEndian = convertRegistersToFloat(-20972, 16391, ByteOrder.BIG_ENDIAN,true);
|
|
|
+// float resultBigEndian1 = convertRegistersToFloat(values[0], values[1], ByteOrder.BIG_ENDIAN,false);
|
|
|
+// System.out.println(resultBigEndian);
|
|
|
+// System.out.println(resultBigEndian1);
|
|
|
+ // 解析为小端序浮点数
|
|
|
+// float resultLittleEndian = convertRegistersToFloat(-20972, 16391, ByteOrder.LITTLE_ENDIAN,true);
|
|
|
+// float resultLittleEndian1 = convertRegistersToFloat(-20972, 16391, ByteOrder.LITTLE_ENDIAN,false);
|
|
|
+// System.out.println(resultLittleEndian);
|
|
|
+// System.out.println(resultLittleEndian1);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // 为从机设置寄存器
|
|
|
+ slave.setDataHolder(holder);
|
|
|
+ // 设置从机的读超时时间,建议主机读的超时时间小于该值
|
|
|
+ slave.setReadTimeout(configuration.getReadTimeout());
|
|
|
+ // 为从机设置从机服务地址slaveId
|
|
|
+ slave.setServerAddress(configuration.getSlaveId());
|
|
|
+ // 开启从机监听事件,必须要这一句
|
|
|
+ slave.listen();
|
|
|
+ }
|
|
|
+
|
|
|
+ public float convertRegistersToFloat(int register1, int register2, ByteOrder byteOrder, boolean isHighRegisterFirst) {
|
|
|
+ // 将两个 16 位寄存器转换为字节数组
|
|
|
+ byte[] bytes = new byte[4];
|
|
|
+ if (isHighRegisterFirst) {
|
|
|
+ // 高字节寄存器在前
|
|
|
+ bytes[0] = (byte) ((register1 >> 8) & 0xFF); // 寄存器1 高字节
|
|
|
+ bytes[1] = (byte) (register1 & 0xFF); // 寄存器1 低字节
|
|
|
+ bytes[2] = (byte) ((register2 >> 8) & 0xFF); // 寄存器2 高字节
|
|
|
+ bytes[3] = (byte) (register2 & 0xFF); // 寄存器2 低字节
|
|
|
+ } else {
|
|
|
+ // 低字节寄存器在前
|
|
|
+ bytes[0] = (byte) ((register2 >> 8) & 0xFF); // 寄存器2 高字节
|
|
|
+ bytes[1] = (byte) (register2 & 0xFF); // 寄存器2 低字节
|
|
|
+ bytes[2] = (byte) ((register1 >> 8) & 0xFF); // 寄存器1 高字节
|
|
|
+ bytes[3] = (byte) (register1 & 0xFF); // 寄存器1 低字节
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用 ByteBuffer 解析字节数组为浮点数
|
|
|
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
|
|
+ buffer.order(byteOrder); // 设置字节顺序
|
|
|
+ return buffer.getFloat();
|
|
|
+ }
|
|
|
+
|
|
|
+ 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<Integer> initValues = configuration.getInitValues();
|
|
|
+ ModbusHoldingRegisters hr = new ModbusHoldingRegisters(initValues.size() + 1);
|
|
|
+ // 修改数值寄存器对应位置的值,第一个参数代表寄存器地址,第二个代表修改的数值
|
|
|
+ //hr.set有几个方法,根据自己要赋值的数据类型选择,此处示例的是赋值float类型,一个float是4个字节,32bit,对应2个寄存器所以i*2
|
|
|
+ for (int i = 0; i < initValues.size(); i++) {
|
|
|
+ Integer value = initValues.get(i);
|
|
|
+ int randomVal = ThreadLocalRandom.current().nextInt(101);
|
|
|
+ value += randomVal;
|
|
|
+ if (value > 20000) {
|
|
|
+ value = 4000;
|
|
|
+ }
|
|
|
+ initValues.set(i, value);
|
|
|
+ hr.setInt32At(i, value);
|
|
|
+ }
|
|
|
+// System.out.println(configuration);
|
|
|
+ slave.getDataHolder().setHoldingRegisters(hr);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void updateInputRegisters(int offset, int quantity) throws IllegalDataAddressException, IllegalDataValueException {
|
|
|
+ /*ModbusHoldingRegisters hr = new ModbusHoldingRegisters(10);
|
|
|
+ // 修改数值寄存器对应位置的值,第一个参数代表寄存器地址,第二个代表修改的数值
|
|
|
+ for (int i = 0; i < LIST.size(); i++) {
|
|
|
+ hr.setFloat32At(i * 2, LIST.get(i));
|
|
|
+ }
|
|
|
+ slave.getDataHolder().setInputRegisters(hr);*/
|
|
|
+ List<Integer> initValues = configuration.getInitValues();
|
|
|
+ ModbusHoldingRegisters hr = new ModbusHoldingRegisters(initValues.size() + 1);
|
|
|
+ for (int i = 0; i < initValues.size(); i++) {
|
|
|
+ Integer value = initValues.get(i);
|
|
|
+ int randomVal = ThreadLocalRandom.current().nextInt(101);
|
|
|
+ value += randomVal;
|
|
|
+ if (value > 20000) {
|
|
|
+ value = 4000;
|
|
|
+ }
|
|
|
+ initValues.set(i, value);
|
|
|
+ hr.setInt32At(i, value);
|
|
|
+ }
|
|
|
+ slave.getDataHolder().setInputRegisters(hr);
|
|
|
+ }
|
|
|
+}
|