杰瑞科技汇

java modbus tcp

Of course! Here is a comprehensive guide to using Modbus TCP in Java, covering the concepts, popular libraries, and complete code examples.

java modbus tcp-图1
(图片来源网络,侵删)

What is Modbus TCP?

Modbus is a serial communication protocol developed in 1979 by Modicon (now Schneider Electric). It's one of the most widely used protocols in industrial automation for connecting electronic devices.

Modbus TCP is simply the adaptation of the Modbus protocol to run on top of TCP/IP, the standard of the modern internet and local networks. This allows you to use standard networking hardware (like Ethernet switches and routers) and simplifies integration with modern IT systems.

Key characteristics:

  • Client/Server Architecture: One device acts as the Master (Client), which requests data. Other devices act as Slaves (Servers), which respond to requests.
  • Simple and Robust: It's a very simple protocol, making it easy to implement and reliable in noisy industrial environments.
  • Open Standard: It's royalty-free, which has contributed to its widespread adoption.

Key Concepts for Modbus TCP in Java

Before diving into code, let's understand the fundamental concepts you'll be working with.

java modbus tcp-图2
(图片来源网络,侵删)
  1. Modbus Function Codes: These are the "verbs" of the Modbus protocol. They define the action the client wants the server to perform.

    • 0x01 (Read Coils): Read discrete outputs (on/off).
    • 0x02 (Read Discrete Inputs): Read discrete inputs (on/off).
    • 0x03 (Read Holding Registers): Read analog output values (16-bit integers).
    • 0x04 (Read Input Registers): Read analog input values (16-bit integers).
    • 0x05 (Write Single Coil): Set a single discrete output on/off.
    • 0x06 (Write Single Register): Set a single analog output value.
    • 0x0F (Write Multiple Coils): Write multiple discrete outputs at once.
    • 0x10 (Write Multiple Registers): Write multiple analog output values at once.
  2. Data Types in Modbus:

    • Coils (Discrete Outputs): Single bits, representing on/off states. They are writable.
    • Discrete Inputs: Single bits, representing on/off states. They are typically read-only.
    • Holding Registers (Analog Outputs): 16-bit (2-byte) registers. They are writable and used to store analog values or configuration data.
    • Input Registers (Analog Inputs): 16-bit (2-byte) registers. They are typically read-only and store data from sensors.
  3. Java Data Types: Modbus registers are 16-bit, so they map to Java's short or int types.

    • A single register is a short (16 bits).
    • To handle 32-bit values (like floats or longs), you need to read two consecutive registers and then swap the bytes (endianness). This is called a "word swap".

Popular Java Libraries for Modbus TCP

You don't need to implement the Modbus TCP protocol from scratch. Several excellent libraries are available.

java modbus tcp-图3
(图片来源网络,侵删)
  1. Jamod (Java Modbus Library): One of the original and most well-known libraries. It's stable and widely used but has a slightly older API style.
  2. Modbus4J: A very popular, modern, and feature-rich library. It's actively maintained and considered by many to be the best choice for new projects. It has a clean API and good documentation.
  3. MB-Utils: A lightweight and simple library, good for basic tasks.
  4. Apache VFS (Virtual File System): While not a dedicated Modbus library, it can access Modbus devices as if they were files, which is a very interesting approach for simple read operations.

Recommendation: For most new projects, Modbus4J is the recommended choice due to its modern design and active community.


Step-by-Step Example with Modbus4J

Let's build a simple application using Modbus4J. We'll create both a Server (Slave) and a Client (Master).

Step 1: Add the Dependency

If you're using Maven, add this to your pom.xml:

<dependency>
    <groupId>com.ghgande</groupId>
    <artifactId>j2mod</artifactId>
    <version>3.2.2</version> <!-- Check for the latest version -->
</dependency>

Note: The original modbus4j project seems to have been forked or integrated into j2mod. The j2mod library is the continuation and is what you should use today.

Step 2: Create a Modbus TCP Server (Slave)

This server will listen for requests on a specific port (e.g., 502) and respond with data from its internal registers.

import com.ghgande.j2mod.modbus.Modbus;
import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction;
import com.ghgande.j2mod.modbus.msg.ReadCoilsRequest;
import com.ghgande.j2mod.modbus.msg.ReadHoldingRegistersRequest;
import com.ghgande.j2mod.modbus.msg.WriteSingleRegisterRequest;
import com.ghgande.j2mod.modbus.net.TCPMasterConnection;
import com.ghgande.j2mod.modbus.slave.ModbusSlave;
import com.ghgande.j2mod.modbus.slave.ModbusSlaveFactory;
import com.ghgande.j2mod.modbus.slave.ModbusSlaveImplementation;
import java.net.InetAddress;
public class ModbusTCPServer {
    public static void main(String[] args) throws Exception {
        // 1. Create a Modbus Slave (Server)
        // Listen on port 502 (the standard Modbus port)
        ModbusSlave slave = ModbusSlaveFactory.createTCPSlave(Modbus.DEFAULT_PORT);
        // 2. Get the implementation to handle requests
        ModbusSlaveImplementation impl = slave.getImplementation();
        // 3. Set up the data for the coils (discrete outputs)
        // We'll have 8 coils (like 8 LEDs)
        impl.addCoilRange(0, 8, new boolean[]{false, false, false, false, false, false, false, false});
        // 4. Set up the data for the holding registers (analog outputs)
        // We'll have 10 registers
        int[] initialRegisters = {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000};
        impl.addRegisterRange(0, 10, initialRegisters);
        System.out.println("Modbus TCP Slave is listening on port " + Modbus.DEFAULT_PORT);
        // 5. Start the slave
        slave.open();
        // Keep the server running
        while (true) {
            Thread.sleep(1000);
            // Optional: Change a register value dynamically for demonstration
            // For example, toggle the first coil every 5 seconds
            if (System.currentTimeMillis() % 5000 < 1000) {
                impl.setCoil(0, true);
            } else {
                impl.setCoil(0, false);
            }
        }
    }
}

Step 3: Create a Modbus TCP Client (Master)

This client will connect to the server and read/write data.

import com.ghgande.j2mod.modbus.Modbus;
import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction;
import com.ghgande.j2mod.modbus.msg.ReadCoilsRequest;
import com.ghgande.j2mod.modbus.msg.ReadHoldingRegistersRequest;
import com.ghgande.j2mod.modbus.msg.WriteSingleRegisterRequest;
import com.ghgande.j2mod.modbus.net.TCPMasterConnection;
import java.net.InetAddress;
public class ModbusTCPClient {
    public static void main(String[] args) throws Exception {
        // 1. Define the server's IP address and port
        InetAddress addr = InetAddress.getByName("127.0.0.1"); // Localhost
        int port = Modbus.DEFAULT_PORT;
        // 2. Create a TCP Master connection
        TCPMasterConnection con = new TCPMasterConnection(addr);
        con.setPort(port);
        con.connect(); // Connect to the slave
        System.out.println("Connected to Modbus TCP Slave at " + addr.getHostAddress());
        // 3. Create a transaction to handle the request/response
        ModbusTCPTransaction trans = new ModbusTCPTransaction(con);
        // --- Example 1: Read Holding Registers (Function Code 0x03) ---
        // Read 5 registers starting from address 0
        ReadHoldingRegistersRequest reqHolding = new ReadHoldingRegistersRequest(0, 5);
        trans.setRequest(reqHolding);
        trans.execute();
        System.out.println("\n--- Reading Holding Registers ---");
        System.out.println("Request: " + reqHolding.getMessage());
        System.out.println("Response: " + trans.getResponse().getMessage());
        // The response contains the data as an array of shorts
        short[] holdingData = ((ReadHoldingRegistersRequest) trans.getResponse()).getRegisterValues();
        for (int i = 0; i < holdingData.length; i++) {
            System.out.println("Register at address " + i + ": " + holdingData[i]);
        }
        // --- Example 2: Read Coils (Function Code 0x01) ---
        // Read 8 coils starting from address 0
        ReadCoilsRequest reqCoils = new ReadCoilsRequest(0, 8);
        trans.setRequest(reqCoils);
        trans.execute();
        System.out.println("\n--- Reading Coils ---");
        System.out.println("Request: " + reqCoils.getMessage());
        System.out.println("Response: " + trans.getResponse().getMessage());
        boolean[] coilData = ((ReadCoilsRequest) trans.getResponse()).getCoilStatus();
        for (int i = 0; i < coilData.length; i++) {
            System.out.println("Coil at address " + i + ": " + (coilData[i] ? "ON" : "OFF"));
        }
        // --- Example 3: Write a Single Register (Function Code 0x06) ---
        // Write the value 1234 to register address 5
        WriteSingleRegisterRequest reqWrite = new WriteSingleRegisterRequest(5, 1234);
        trans.setRequest(reqWrite);
        trans.execute();
        System.out.println("\n--- Writing a Single Register ---");
        System.out.println("Wrote value 1234 to register address 5.");
        System.out.println("Request: " + reqWrite.getMessage());
        System.out.println("Response: " + trans.getResponse().getMessage());
        // --- Verify the write ---
        reqHolding = new ReadHoldingRegistersRequest(5, 1); // Read just the one we wrote
        trans.setRequest(reqHolding);
        trans.execute();
        short writtenValue = ((ReadHoldingRegistersRequest) trans.getResponse()).getRegisterValues()[0];
        System.out.println("Verification: Value at register 5 is now: " + writtenValue);
        // 4. Close the connection
        con.close();
        System.out.println("\nConnection closed.");
    }
}

How to Run the Example

  1. Create a new Java project in your favorite IDE (IntelliJ, Eclipse, etc.).
  2. Add the j2mod dependency to your pom.xml as shown in Step 1.
  3. Create two Java files: ModbusTCPServer.java and ModbusTCPClient.java and paste the code from above.
  4. Run the Server first: Right-click on ModbusTCPServer.java and select "Run".
  5. Run the Client next: Right-click on ModbusTCPClient.java and select "Run".

You will see the client connect to the server, perform read and write operations, and print the results to the console.


Advanced Topics

Reading and Writing 32-bit Floats

Modbus only handles 16-bit values. To work with 32-bit floats, you must read two consecutive registers and then perform a word swap and byte reordering.

Important: You must know the endianness of the device. Most devices use Big-Endian (most significant byte first).

import com.ghgande.j2mod.modbus.msg.ReadHoldingRegistersRequest;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
// ... inside your client code ...
// Read two consecutive registers to get a 32-bit float
ReadHoldingRegistersRequest reqFloat = new ReadHoldingRegistersRequest(2, 2); // Read from address 2
trans.setRequest(reqFloat);
trans.execute();
short[] registerValues = ((ReadHoldingRegistersRequest) trans.getResponse()).getRegisterValues();
// The registers come in the order [low, high] for a Big-Endian float
// We need to swap them to [high, low] and then convert to bytes
byte[] bytes = new byte[4];
bytes[0] = (byte) (registerValues[1] >> 8); // High byte of high register
bytes[1] = (byte) (registerValues[1]);      // Low byte of high register
bytes[2] = (byte) (registerValues[0] >> 8); // High byte of low register
bytes[3] = (byte) (registerValues[0]);      // Low byte of low register
// Convert the byte array to a float
float floatValue = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
System.out.println("\n--- Reading a 32-bit Float ---");
System.out.println("Float value read from registers 2 & 3: " + floatValue);

Error Handling

Always check the response for errors. A successful response will have a function code that is the same as the request. An error response will have the function code with the most significant bit set (e.g., 0x83 for an error in response to a 0x03 request).

trans.execute();
if (trans.getResponse().isException()) {
    System.err.println("Error: " + trans.getResponse().getExceptionCode());
} else {
    // Process successful response
    System.out.println("Success: " + trans.getResponse().getMessage());
}
分享:
扫描分享到社交APP
上一篇
下一篇