Jamod(Java Modbus)是Java生态中历史最悠久、最经典的Modbusプロトコル実装库,由Dieter Wimberger开发,广泛应用于工业自動化、Data Acquisition、SCADA系统等领域。JamodサポートModbus TCP、RTU和ASCII三种协议,既可作为クライアント側は也可作为サーバー側。。本記事详细介绍使い方は?Jamod库実装Modbus TCP Server(slave),包括环境設定、データストア、リクエスト処理、多クライアント側は管理以及完全的サンプルコード。
一、Jamod库概述
1.1 核心特性
- 多协议サポート:Modbus TCP、RTU、ASCII3つの伝送モード
- 主从兼备:既可作为クライアント側は(master),也可作为サーバー側。(slave)
- 功能完全:サポート01-16すべての標準機能コード
- 事件驱动:サポートデータ变化监听、连接ステータス回调
- 跨プラットフォーム:Windows、Linux、Mac OS均可运行
- 成熟稳定:经过多年工业现场验证,bug少,稳定性高
- 开源無料:BSDProtocol,可無料用于商业项目
1.2 Maven依赖設定
<!-- pom.xml -->
<dependency>
<groupId>net.wimpi</groupId>
<artifactId>jamod</artifactId>
<version>1.2</version>
</dependency>
<!-- シリアルポートサポート(RTUモード需要) -->
<dependency>
<groupId>org.rxtx</groupId>
<artifactId>rxtx</artifactId>
<version>2.1.7</version>
</dependency>
1.3 核心类説明
| 类名 | Function |
|---|---|
| ModbusTCPListener | TCP监听器,接受クライアント側は连接 |
| ModbusCoupler | Modbus耦合器,管理データストア |
| SimpleProcessImage | 简单进程映像,存储レジスタ和コイルデータ |
| DigitalOut | 数字出力(コイル) |
| DigitalIn | 数字入力(ショップ型入力) |
| Register | レジスタ基类 |
| SimpleRegister | 简单レジスタ実装 |
| InputRegister | 入力レジスタ。 |
二、基础Modbus TCP Server実装
2.1 最简サーバー側。
import net.wimpi.modbus.Modbus;
import net.wimpi.modbus.net.ModbusTCPListener;
import net.wimpi.modbus.procimg.SimpleProcessImage;
import net.wimpi.modbus.procimg.SimpleRegister;
import net.wimpi.modbus.procimg.SimpleDigitalOut;
import net.wimpi.modbus.procimg.SimpleDigitalIn;
import net.wimpi.modbus.ModbusCoupler;
public class SimpleModbusServer {
public static void main(String[] args) {
try {
// 1. 作成进程映像(データストア)
SimpleProcessImage processImage = new SimpleProcessImage();
// 追加数字出力(コイル)- 16个
for (int i = 0; i < 16; i++) {
processImage.addDigitalOut(new SimpleDigitalOut(false));
}
// 追加数字入力(ショップ型入力)- 16个
for (int i = 0; i < 16; i++) {
processImage.addDigitalIn(new SimpleDigitalIn(false));
}
// 追加レジスタを保持する。 - 32个
for (int i = 0; i < 32; i++) {
processImage.addRegister(new SimpleRegister(0));
}
// 追加入力レジスタ。 - 16个
for (int i = 0; i < 16; i++) {
processImage.addInputRegister(new SimpleRegister(0));
}
// 2. 設定初期値は
processImage.getRegister(0).setValue(100); // temperature
processImage.getRegister(1).setValue(50); // 湿度
processImage.getDigitalOut(0).set(true); // Relay1
// 3. 作成Modbus耦合器并設定进程映像
ModbusCoupler.getReference().setProcessImage(processImage);
ModbusCoupler.getReference().setMaster(false); // 駅からのモデル
ModbusCoupler.getReference().setUnitID(1); // slave address
// 4. 作成TCP监听器
ModbusTCPListener listener = new ModbusTCPListener(3); // 3个线程処理连接
listener.setPort(502);
listener.start();
System.out.println("Modbus TCP Server已启动,监听ポート:502");
System.out.println("slave address:1");
System.out.println("コイル:16个,ショップ型入力:16个");
System.out.println("レジスタを保持する。:32个,入力レジスタ。:16个");
// 保持主线程运行
Thread.currentThread().join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2 代码説明
- SimpleProcessImage:进程映像,存储すべてのModbusdata(コイル、入力、register)
- SimpleDigitalOut:数字出力,对应Modbusコイル(function code01/05/15)
- SimpleDigitalIn:数字入力,对应ショップ型入力(function code02)
- SimpleRegister:レジスタを保持する。(function code03/06/16)和入力レジスタ。(function code04)
- ModbusCoupler:单例モード,全局管理进程映像和ステーションからの設定
- ModbusTCPListener:TCP监听器,参数为処理连接的线程数
三、自定义进程映像
在实际项目中,通常需要自定义进程映像,将Modbusデータ与实际硬件IO关联起来。以下サンプル表示如何作成自定义进程映像,実装データ与硬件的連動。
import net.wimpi.modbus.procimg.*;
public class CustomProcessImage extends SimpleProcessImage {
// 硬件接口引用(example)
private HardwareIO hardware;
public CustomProcessImage(HardwareIO hw) {
super();
this.hardware = hw;
// 初期化数字出力
for (int i = 0; i < 8; i++) {
addDigitalOut(new HardwareDigitalOut(i, hw));
}
// 初期化数字入力
for (int i = 0; i < 8; i++) {
addDigitalIn(new HardwareDigitalIn(i, hw));
}
// 保持レジスタの初期化
for (int i = 0; i < 32; i++) {
addRegister(new SimpleRegister(0));
}
// 初期化入力レジスタ。(ADC)
for (int i = 0; i < 8; i++) {
addInputRegister(new HardwareInputRegister(i, hw));
}
}
/**
* 自定义数字出力:写入时同步控制硬件继电器
*/
class HardwareDigitalOut implements DigitalOut {
private int channel;
private HardwareIO hw;
private boolean state = false;
public HardwareDigitalOut(int channel, HardwareIO hw) {
this.channel = channel;
this.hw = hw;
}
@Override
public boolean isSet() {
return state;
}
@Override
public void set(boolean b) {
state = b;
hw.setRelay(channel, b); // 控制硬件继电器
}
}
/**
* 自定义数字入力:読み取り时从硬件取得ステータス
*/
class HardwareDigitalIn implements DigitalIn {
private int channel;
private HardwareIO hw;
public HardwareDigitalIn(int channel, HardwareIO hw) {
this.channel = channel;
this.hw = hw;
}
@Override
public boolean isSet() {
return hw.getInput(channel); // 読み取り硬件入力ステータス
}
}
/**
* 自定义入力レジスタ。:読み取り时从ADC取得值
*/
class HardwareInputRegister implements InputRegister {
private int channel;
private HardwareIO hw;
public HardwareInputRegister(int channel, HardwareIO hw) {
this.channel = channel;
this.hw = hw;
}
@Override
public int getValue() {
return hw.readADC(channel); // readADC值
}
@Override
public int toUnsignedShort() {
return getValue();
}
@Override
public short toShort() {
return (short) getValue();
}
@Override
public byte[] toBytes() {
int val = getValue();
return new byte[]{(byte)(val >> 8), (byte)(val & 0xFF)};
}
}
}
// 硬件接口(example)
interface HardwareIO {
void setRelay(int channel, boolean state);
boolean getInput(int channel);
int readADC(int channel);
}
四、带データ更新的サーバー側。
import net.wimpi.modbus.net.ModbusTCPListener;
import net.wimpi.modbus.procimg.SimpleProcessImage;
import net.wimpi.modbus.procimg.SimpleRegister;
import net.wimpi.modbus.procimg.SimpleDigitalOut;
import net.wimpi.modbus.ModbusCoupler;
import java.util.Random;
public class DataUpdateServer {
private static SimpleProcessImage processImage;
private static volatile boolean running = true;
public static void main(String[] args) {
try {
// 作成进程映像
processImage = new SimpleProcessImage();
// 8路继电器出力
for (int i = 0; i < 8; i++) {
processImage.addDigitalOut(new SimpleDigitalOut(false));
}
// 8路开关量入力
for (int i = 0; i < 8; i++) {
processImage.addDigitalIn(new SimpleDigitalIn(false));
}
// 32レジスタを保持する。
for (int i = 0; i < 32; i++) {
processImage.addRegister(new SimpleRegister(0));
}
// 8路ADC入力レジスタ。
for (int i = 0; i < 8; i++) {
processImage.addInputRegister(new SimpleRegister(0));
}
// 設定初期値は
processImage.getRegister(0).setValue(9600); // ボーレート参数
processImage.getRegister(1).setValue(1); // slave address
processImage.getRegister(2).setValue(250); // 温度の設定值
// 設定耦合器
ModbusCoupler.getReference().setProcessImage(processImage);
ModbusCoupler.getReference().setMaster(false);
ModbusCoupler.getReference().setUnitID(1);
// 启动TCP监听器
ModbusTCPListener listener = new ModbusTCPListener(5);
listener.setPort(502);
listener.start();
System.out.println("Modbus TCP Server已启动,ポート:502");
// 启动データ更新线程
Thread updateThread = new Thread(() -> {
Random random = new Random();
while (running) {
try {
// 模拟センサーはデータ更新
int temp = 200 + random.nextInt(150); // 20.0-35.0℃
int humidity = 400 + random.nextInt(300); // 40.0-70.0%
int pressure = 1000 + random.nextInt(50); // 1000-1050hPa
processImage.getInputRegister(0).setValue(temp);
processImage.getInputRegister(1).setValue(humidity);
processImage.getInputRegister(2).setValue(pressure);
// 更新ADC通道
for (int i = 0; i < 8; i++) {
processImage.getInputRegister(i).setValue(random.nextInt(4096));
}
// 更新开关量入力(模拟)
for (int i = 0; i < 8; i++) {
processImage.getDigitalIn(i).set(random.nextBoolean());
}
// チェックレジスタを保持する。中的控制命令
int cmd = processImage.getRegister(10).getValue();
if (cmd == 1) {
System.out.println("收到スタートアップコマンド");
processImage.getRegister(10).setValue(0); // クリア命令
} else if (cmd == 2) {
System.out.println("收到停止命令");
processImage.getRegister(10).setValue(0);
}
Thread.sleep(1000); // 每秒更新一次
} catch (Exception e) {
e.printStackTrace();
}
}
});
updateThread.start();
// 登録关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
running = false;
listener.stop();
System.out.println("サーバー·サーバー停止した。");
}));
Thread.currentThread().join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
五、多駅からの住所サポート
import net.wimpi.modbus.net.ModbusTCPListener;
import net.wimpi.modbus.procimg.SimpleProcessImage;
import net.wimpi.modbus.procimg.SimpleRegister;
import net.wimpi.modbus.ModbusCoupler;
import java.util.HashMap;
import java.util.Map;
public class MultiUnitServer {
public static void main(String[] args) {
try {
// 作成多个进程映像,对应不同駅からの住所
Map<Integer, SimpleProcessImage> images = new HashMap<>();
// slave1:Temperature Sensor
SimpleProcessImage img1 = new SimpleProcessImage();
img1.addInputRegister(new SimpleRegister(250)); // temperature
img1.addInputRegister(new SimpleRegister(600)); // 湿度
images.put(1, img1);
// slave2:继电器モジュール
SimpleProcessImage img2 = new SimpleProcessImage();
for (int i = 0; i < 8; i++) {
img2.addDigitalOut(new SimpleDigitalOut(false));
}
images.put(2, img2);
// slave3:PLC
SimpleProcessImage img3 = new SimpleProcessImage();
for (int i = 0; i < 16; i++) {
img3.addRegister(new SimpleRegister(0));
}
images.put(3, img3);
// 注意:Jamod的ModbusCoupler是单例,只サポート一个駅からの住所
// 如需多スレーブ,需要自定义ModbusTCPListenerまたは使用其他库
// 此处以スレーブ1为例
ModbusCoupler.getReference().setProcessImage(img1);
ModbusCoupler.getReference().setMaster(false);
ModbusCoupler.getReference().setUnitID(1);
ModbusTCPListener listener = new ModbusTCPListener(3);
listener.setPort(502);
listener.start();
System.out.println("多スレーブサーバー·サーバー已启动");
System.out.println("slave1:Temperature Sensor");
System.out.println("slave2:继电器モジュール");
System.out.println("slave3:PLC");
Thread.currentThread().join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
六、使用Jamodクライアント側は测试サーバー側。
import net.wimpi.modbus.Modbus;
import net.wimpi.modbus.io.ModbusTCPTransaction;
import net.wimpi.modbus.msg.*;
import net.wimpi.modbus.net.TCPMasterConnection;
import java.net.InetAddress;
public class ModbusClientTest {
public static void main(String[] args) {
try {
// 连接到サーバー·サーバー
InetAddress addr = InetAddress.getByName("127.0.0.1");
TCPMasterConnection connection = new TCPMasterConnection(addr);
connection.setPort(502);
connection.connect();
System.out.println("接続しましたサーバー·サーバー");
// 設定駅からの住所
connection.setTimeout(3000);
// 1. read holding registers(function code03)
System.out.println("\n=== read holding registers ===");
ReadMultipleRegistersRequest readReq =
new ReadMultipleRegistersRequest(0, 10);
readReq.setUnitID(1);
ModbusTCPTransaction trans = new ModbusTCPTransaction(connection);
trans.setRequest(readReq);
trans.execute();
ReadMultipleRegistersResponse readResp =
(ReadMultipleRegistersResponse) trans . get Res ponse () ;
for (int i = 0; i
System . out . print ln (" 레 지 스터 " + i + " :" + read R esp . get Reg ister V alue (i));
}
/ / 2.단일 레 지 스터 쓰 기 (기 능 코드 0 6)
System . out . print ln (" \ n == = 단일 레 지 스터 를 쓰 기 == =");
Write Sing le Reg ister Re quest Write Re q =
새로운 Write Sing le Reg ister Re quest (5, 123 4) ;
write Re q . set Unit ID (1) ;
trans . set Re quest (wr ite Re q)
trans . execu te () ;
System . out . print ln (" 기 입 레 지 스터 5: 12 34 ");
// 3.읽기 코 일 (기 능 코드 0 1)
System . out . print ln ( " \ n == = 읽기 코 일 == =");
Read Co ils Re quest co ils Re q = new Read Co ils Re quest (0, 8);
co ils Re q . set Unit ID (1) ;
trans . set Re quest (co ils Re q);
trans . execu te () ;
Read Co ils Res ponse co ils R esp = (Read Co ils Res ponse) trans . get Res ponse ();
for (int i = 0; i
System . out . print ln (" Co il " + i + " :" + co ils R esp . get Co il (i));
}
/ / 4개별 코 일 쓰 기 (기 능 코드 0 5)
System . out . print ln ( " \ n == = 단일 코 일 작성 == =);
Write Co il Re quest write Co il Re q = new Write Co il Re quest (0, true);
write Co il Re q . set Unit ID (1) ;
trans . set Re quest (wr ite Co il Re q);
trans . execu te () ;
System . out . print ln (" 코 일 0 설정 ");
/ / 5.읽기 입력 레 지 스터 (기 능 코드 0 4)
System . out . print ln ( " \ n == = 입력 레 지 스터 읽기 == =);
Read In put Reg ister s Re quest input Re q =
새 Read In put Reg ister s Re quest (0, 4);
input Re q . set Unit ID (1) ;
trans . set Re quest (in put Re q)
trans . execu te () ;
Read In put Reg ister s Res ponse input Res p =
(Read In put Reg ister s Res ponse) trans . get Res ponse ();
for (int i = 0; i
System . out . print ln (" 입 력 레 지 스터 " + i + " :" + input R esp . get Reg ister V alue (i));
}
connection . close ();
System . out . print ln (" \ n 연결 이 닫 혔 습니다 ");
} catch (Ex ception e) {
e . print Sta ck Tra ce () ;
}
}
}
일반적인 질문 과 모 범 사례
7. 1 자주 묻는 질문
- ポート502被占用
- クライアント側は连接被拒绝
- データ不更新
- 多スレーブサポートなし。
- 并发問題
- RTUモード报错
7. 2 모 범 사례
- 合理設定线程数
- 使用关闭钩子
- データ更新独立线程
- ジャーナル·レコード
- 設定外部化
- データ持久化
- 心跳检测
Jam od 와 다른 Java Mod bus 라이 브 러 리 비교
| 库名 | ||||
|---|---|---|---|---|
| Jamod | ||||
| EasyModbus | ||||
| j2mod | ||||
| Modbus4J |
Java Mod bus 에서 개발 된 고 전 적인 라이 브 러 리 인 Jam od 는 업데이트 가 느 리 지만 안정 성과 신뢰 성은 수 년간 업 계 현 장에서 검 증 되었습니다 .이 문서 에서 제공 되는 코 드는프로젝트 개발에 직접 사용되며 실제 환경에 배포하기 전에 로컬 테스트를 통과하는 것이 좋습니다.새로운 프로젝트의 경우 Easy Modbus 와 같은보다 활동적인 라이브러리를 사용하는 것도 고려할 수 있습니다.
Leave a Reply