소스 검색

转从站

hjp 3 달 전
부모
커밋
f63da28ae4

+ 6 - 0
pom.xml

@@ -92,6 +92,12 @@
             <version>${modbus4j.version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>com.intelligt.modbus</groupId>
+            <artifactId>jlibmodbus</artifactId>
+            <version>1.2.9.7</version>
+        </dependency>
+
         <!-- webSocket 开始-->
         <dependency>
             <groupId>org.springframework.boot</groupId>

+ 29 - 0
src/main/java/com/jg/config/ModBusSlaveConfiguration.java

@@ -0,0 +1,29 @@
+package com.jg.config;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+/**
+ * <p>
+ * </p>
+ *
+ * @author lxp
+ * @version V1.0
+ * @since 2023/6/2  17:00
+ */
+@Slf4j
+@Data
+@Configuration
+@ConfigurationProperties("mod-bus.config")
+public class ModBusSlaveConfiguration {
+
+    private String modBusHost;
+    private Integer modBusPort;
+    private Integer slaveId;
+    private Integer readTimeout;
+    private List<Integer> initValues;
+}

+ 330 - 0
src/main/java/com/jg/config/ModBusSlaveServer.java

@@ -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);
+    }
+}

+ 30 - 0
src/main/java/com/jg/service/ModBusEventListener.java

@@ -0,0 +1,30 @@
+package com.jg.service;
+
+/**
+ * <p>
+ * </p>
+ *
+ * @author lxp
+ * @version V1.0
+ * @since 2023/6/2  15:30
+ */
+public interface ModBusEventListener {
+
+    /**
+     * 读保存寄存器
+     * @param offset 偏移
+     * @param quantity 数量
+     * @return []
+     */
+    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);
+}

+ 99 - 0
src/main/java/com/jg/service/MyOwnDataHolder.java

@@ -0,0 +1,99 @@
+package com.jg.service;
+
+import com.intelligt.modbus.jlibmodbus.data.DataHolder;
+import com.intelligt.modbus.jlibmodbus.exception.IllegalDataAddressException;
+import com.intelligt.modbus.jlibmodbus.exception.IllegalDataValueException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ *     功能码
+ *          0x01: 读线圈寄存器
+ *          0x02: 读离散输入寄存器
+ *          0x03: 读保持寄存器
+ *          0x04: 读输入寄存器
+ *          0x05: 写单个线圈寄存器
+ *          0x06: 写单个保持寄存器
+ *          0x0f: 写多个线圈寄存器
+ *          0x10: 写多个保持寄存器
+ * </p>
+ *
+ * @author lxp
+ * @version V1.0
+ * @since 2023/6/2  15:31
+ */
+public class MyOwnDataHolder extends DataHolder {
+
+    final List<ModBusEventListener> modBusEventListenerList = new ArrayList<>();
+
+    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);
+    }
+
+}

+ 4 - 4
src/main/java/com/jg/service/impl/ModBusServiceImpl.java

@@ -50,7 +50,7 @@ public class ModBusServiceImpl implements ModBusService {
     /**
      * 创建ModBusTCP连接
      */
-    @EventListener(ApplicationReadyEvent.class)
+//    @EventListener(ApplicationReadyEvent.class)
     private void createConn() {
         // 创建ModBus连接, 存入MASTER_MAP
 //        List<CollectModuleInfo> collectModuleInfoList = collectModuleInfoService.findCurrent();
@@ -71,8 +71,8 @@ public class ModBusServiceImpl implements ModBusService {
 ////            throw new BizException("连接失败! 没有查询到ModBus模块信息!");
 //        }
 //        log.warn("[开始连接采集模块]: {}", JSON.toJSONString(collectModuleInfoList));
-        ModbusMaster master = ModBusUtil.createMaster(host, port);
-        MASTER_MAP.put(1l, master);
+//        ModbusMaster master = ModBusUtil.createMaster(host, port);
+//        MASTER_MAP.put(1l, master);
         InputStream inputStream = getClass().getClassLoader().getResourceAsStream("tag.json");
         try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()){
                 byte[] buffer = new byte[1024];
@@ -87,7 +87,7 @@ public class ModBusServiceImpl implements ModBusService {
         }
     }
 
-    @Scheduled(fixedRate = 1000)
+//    @Scheduled(fixedRate = 1000)
     private void readTagsValue(){
         List<TagVo> tagVos = JSON.parseArray(show, TagVo.class);
         BatchRead<String> batch = new BatchRead<>();

+ 1 - 1
src/main/java/com/jg/util/ModBusUtil.java

@@ -43,7 +43,7 @@ public class ModBusUtil {
             master.setRetries(3);
             return master;
         } catch (Exception e) {
-            log.error("连接失败, Modbus Tcp Connection Info ModbusSlaveInfo: {}", JSON.toJSONString("collectModuleInfo"));
+            log.error("连接失败, Modbus Tcp Connection Info ModbusSlaveInfo: {}", JSON.toJSONString(host));
             throw new RuntimeException(e);
         }
     }

+ 1 - 1
src/main/java/com/jg/vo/ShowVo.java

@@ -13,7 +13,7 @@ public class ShowVo {
 
     public String name;
 
-    public BigDecimal value;
+    public BigDecimal value = BigDecimal.ZERO;
 
     public String unit;
 }

+ 1 - 1
src/main/resources/application-dev.yml

@@ -1,3 +1,3 @@
 modbus:
   host: 192.168.0.100
-  port: 8234
+  port: 502

+ 48 - 0
src/main/resources/application.yml

@@ -72,3 +72,51 @@ management:
   endpoint:
     health:
       show-details: ALWAYS
+
+mod-bus:
+  config:
+    slave-id: 1
+    modBus-host: 192.168.1.30
+    modBus-port: 502
+    read-timeout: 1500
+    init-values:
+      - 4200
+      - 4400
+      - 4600
+      - 4800
+      - 5000
+      - 5200
+      - 5400
+      - 5600
+      - 5800
+      - 6000
+      - 8000
+      - 8200
+      - 8600
+      - 8800
+      - 9000
+      - 9200
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600
+      - 9600

+ 565 - 7
src/main/resources/tag.json

@@ -2,18 +2,576 @@
   {
     "offset": 0,
     "slaveId": 1,
-    "functionCode": 3,
-    "tag": "1",
-    "desc": "温度",
+    "functionCode": 4,
+    "tag": "ESA_PV001",
+    "desc": "1#支管煤气开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 2,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV002",
+    "desc": "2#支管煤气开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 4,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV003",
+    "desc": "3#支管煤气开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 6,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV004",
+    "desc": "4#支管煤气开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 8,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV005",
+    "desc": "5#支管煤气开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 10,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV006",
+    "desc": "6#支管煤气开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 12,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV007",
+    "desc": "7#支管煤气开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 14,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV008",
+    "desc": "8#支管煤气开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 16,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV009",
+    "desc": "1#引风机循环热风开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 18,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV0010",
+    "desc": "1#引风机炉膛热风开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 20,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV0011",
+    "desc": "2#引风机循环热风开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 22,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV0012",
+    "desc": "2#引风机炉膛热风开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 24,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV0013",
+    "desc": "3#引风机循环热风开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 26,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV0014",
+    "desc": "3#引风机炉膛热风开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 28,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV0015",
+    "desc": "4#引风机循环热风开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 30,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_PV0016",
+    "desc": "4#引风机炉膛热风开度",
+    "unit": "%",
+    "dataType": "int"
+  },
+  {
+    "offset": 32,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_01",
+    "desc": "西库侧面1#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 34,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_02",
+    "desc": "西库侧面2#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 36,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_03",
+    "desc": "西库侧面3#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 38,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_04",
+    "desc": "西库侧面4#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 40,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_05",
+    "desc": "西库侧面5#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 42,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_06",
+    "desc": "西库侧面6#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 44,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_07",
+    "desc": "西库侧面7#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 46,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_08",
+    "desc": "西库侧面8#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 48,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_09",
+    "desc": "东库侧面1#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 50,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_10",
+    "desc": "东库侧面2#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 52,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_11",
+    "desc": "东库侧面3#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 54,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_12",
+    "desc": "东库侧面4#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 56,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_13",
+    "desc": "东库侧面5#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 58,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_14",
+    "desc": "东库侧面6#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 60,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_15",
+    "desc": "东库侧面7#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 62,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_16",
+    "desc": "东库侧面8#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 64,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_17",
+    "desc": "西库侧面9#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 66,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_18",
+    "desc": "西库侧面10#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 68,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_19",
+    "desc": "西库上面1#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 70,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_20",
+    "desc": "西库上面2#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 72,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_21",
+    "desc": "西库上面3#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 74,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_22",
+    "desc": "西库上面4#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 76,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_23",
+    "desc": "西库上面5#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 78,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_24",
+    "desc": "西库上面6#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 80,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_25",
+    "desc": "东库侧面9#温度",
     "unit": "℃",
     "dataType": "int"
   },
   {
-    "offset": 1,
+    "offset": 82,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_26",
+    "desc": "东库侧面10#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 84,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_27",
+    "desc": "东库上面1#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 86,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_28",
+    "desc": "东库上面2#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 88,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_29",
+    "desc": "东库上面3#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 90,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_30",
+    "desc": "东库上面4#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 92,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_31",
+    "desc": "东库上面5#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 94,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_32",
+    "desc": "东库上面6#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 96,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_33",
+    "desc": "东库上面7#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 98,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_34",
+    "desc": "东库上面8#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 100,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_35",
+    "desc": "东库上面9#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 102,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_36",
+    "desc": "东库上面10#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 104,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_37",
+    "desc": "南燃烧室西侧温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 106,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_38",
+    "desc": "北燃烧室东侧温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 108,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_39",
+    "desc": "北燃烧室西侧温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 110,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_40",
+    "desc": "南燃烧室东侧温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 112,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_41",
+    "desc": "东库上面7#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 114,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_42",
+    "desc": "东库上面8#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 116,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_43",
+    "desc": "东库上面9#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 118,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_44",
+    "desc": "东库上面10#温度",
+    "unit": "℃",
+    "dataType": "int"
+  },
+  {
+    "offset": 120,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_45",
+    "desc": "北风机入口压力",
+    "unit": "Pa",
+    "dataType": "int"
+  },
+  {
+    "offset": 122,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_46",
+    "desc": "北风机出口压力",
+    "unit": "Pa",
+    "dataType": "int"
+  },
+  {
+    "offset": 124,
+    "slaveId": 1,
+    "functionCode": 4,
+    "tag": "ESA_47",
+    "desc": "南风机入口压力",
+    "unit": "Pa",
+    "dataType": "int"
+  },
+  {
+    "offset": 126,
     "slaveId": 1,
-    "functionCode": 3,
-    "tag": "2",
-    "desc": "压力",
+    "functionCode": 4,
+    "tag": "ESA_48",
+    "desc": "南风机出口压力",
     "unit": "Pa",
     "dataType": "int"
   }