Modus協議是由MODICON(現爲施耐德電氣公司的一個品牌)在1979年開發的,是全球第一個真正用於工業現場的總線協議,應用很是普遍,可謂大名鼎鼎。html
理論性的東西就很少介紹了,推薦一本書《Modbus軟件開發實戰指南》,楊更更著,寫得很是好,從理論到實戰,手把手教你玩轉Modbus,不過代碼實戰部分使用的是C#,筆者沒練過這項武功,仍是看一下Java中怎麼應用吧,網上資料多用Modbus4J,就選它了。java
Modbus4J源代碼:https://github.com/infiniteautomation/modbus4jgit
Modbus4J沒有提供底層串口驅動,所以須要先掌握一些Java串口編程的能力,快速入門能夠參考筆者以前寫的《Java串口編程例子》這篇文章,本文就是在此基礎上進行的,也應用了其中的串口編程工具類代碼。github
新建modbus4j項目,以下圖所示:編程
源代碼地址:https://github.com/wu-boy/modbus4j.gitapp
Modbus4J沒有提供底層串口驅動,所以使用串口工具類SerialPortUtils來打開和關閉串口,代碼以下:dom
public class SerialPortUtils { private static Logger log = LoggerFactory.getLogger(SerialPortUtils.class); /** * 打卡串口 * @param portName 串口名 * @param baudRate 波特率 * @param dataBits 數據位 * @param stopBits 中止位 * @param parity 校驗位 * @return 串口對象 */ public static SerialPort open(String portName, Integer baudRate, Integer dataBits, Integer stopBits, Integer parity) { SerialPort result = null; try { // 經過端口名識別端口 CommPortIdentifier identifier = CommPortIdentifier.getPortIdentifier(portName); // 打開端口,並給端口名字和一個timeout(打開操做的超時時間) CommPort commPort = identifier.open(portName, 2000); // 判斷是否是串口 if (commPort instanceof SerialPort) { result = (SerialPort) commPort; // 設置一下串口的波特率等參數 result.setSerialPortParams(baudRate, dataBits, stopBits, parity); log.info("打開串口{}成功", portName); }else{ log.info("{}不是串口", portName); } } catch (Exception e) { log.error("打開串口{}錯誤", portName, e); } return result; } /** * 關閉串口 * @param serialPort */ public static void close(SerialPort serialPort) { if (serialPort != null) { serialPort.close(); log.warn("串口{}關閉", serialPort.getName()); } } }
Modbus4J提供了串口包裝器接口,可是沒有提供實現,所以本身新建一個實現類SerialPortWrapperImpl,做用是爲Modbus4J提供串口對象SerialPort和操做串口的方法,例如打開/關閉串口,獲取串口輸入/輸出流等,核心代碼以下:ide
RtuSlaveTest類模擬了一個地址爲1的從站設備,使用串口「COM2「(請提早使用虛擬串口軟件Virtual Serial Port Driver模擬出來COM1和COM2串口),經過ModbusFactory建立RtuSlave,而後模擬線圈狀態、離散輸入狀態、保持寄存器和輸入寄存器的數據,代碼中有詳細註釋,代碼以下:工具
public class RtuSlaveTest { public static void main(String[] args) { createRtuSlave(); } public static void createRtuSlave(){ // 設置串口參數,串口是COM2,波特率是9600 SerialPortWrapperImpl wrapper = new SerialPortWrapperImpl("COM2", 9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE, 0, 0); // Modbus工廠,能夠建立RTU、TCP等不一樣類型的Master和Slave ModbusFactory modbusFactory = new ModbusFactory(); final ModbusSlaveSet slave = modbusFactory.createRtuSlave(wrapper); // 這玩意網上有人叫作過程影像區,其實就是寄存器 // 寄存器裏能夠設置線圈狀態、離散輸入狀態、保持寄存器和輸入寄存器 // 這裏設置了從站設備ID是1 BasicProcessImage processImage = new BasicProcessImage(1); processImage.setInvalidAddressValue(Short.MIN_VALUE); slave.addProcessImage(processImage); // 添加監聽器,監聽slave線圈狀態和保持寄存器的寫入 processImage.addListener(new MyProcessImageListener()); setCoil(processImage); setInput(processImage); setHoldingRegister(processImage); setInputRegister(processImage); // 開啓線程啓動從站設備 new Thread(() -> { try { slave.start(); } catch (ModbusInitException e) { e.printStackTrace(); } }).start(); /*new Timer().schedule(new TimerTask() { @Override public void run() { // 間隔1秒修改從站設備1的保持寄存器數據 updateHoldingRegister(slave.getProcessImage(1)); } }, 1000, 1000);*/ } private static void setCoil(ProcessImage processImage){ // 模擬線圈狀態 processImage.setCoil(0, true); processImage.setCoil(1, false); processImage.setCoil(2, true); } private static void setInput(ProcessImage processImage){ // 模擬離散輸入狀態 processImage.setInput(0, false); processImage.setInput(1, true); processImage.setInput(2, false); } private static void setHoldingRegister(ProcessImage processImage){ // 模擬保持寄存器的值 processImage.setHoldingRegister(0,(short) 11); processImage.setHoldingRegister(1,(short) 22); processImage.setHoldingRegister(2,(short) 33); } private static void updateHoldingRegister(ProcessImage processImage){ // 模擬修改保持寄存器的值 processImage.setHoldingRegister(0, (short) RandomUtil.randomInt(0, 100)); processImage.setHoldingRegister(1,(short) RandomUtil.randomInt(0, 100)); processImage.setHoldingRegister(2,(short) RandomUtil.randomInt(0, 100)); } private static void setInputRegister(ProcessImage processImage){ // 模擬輸入寄存器的值 processImage.setInputRegister(0,(short) 44); processImage.setInputRegister(1,(short) 55); processImage.setInputRegister(2,(short) 66); } }
Modbus Poll和Modbus Slave分別是主站設備仿真工具和從站設備仿真工具,是Modbus開發最經常使用的兩個測試軟件,下載地址:https://www.modbustools.com/測試
網上最近出現了一個國產軟件Mthings,可以同時支持模擬主從機功能,聽說功能強大還有使用手冊,免安裝無償使用!筆者因爲參考了《Modbus軟件開發實戰指南》這本書,就沒使用Mthings,有興趣的同窗能夠試用。
下載安裝後,打開鏈接參數進行設置,以下圖所示:
RtuSlaveTest類使用了串口COM2來模擬從站設備,所以這裏選擇COM1,選擇RTU模式,點擊OK。
選擇菜單【Setup】->【Read/Write Definition…】,以下圖所示:
設置從站設備地址爲1,功能碼03是讀取保持寄存器數據,寄存器地址爲0,數量爲3,由於RtuSlaveTest程序中模擬了3個數據,點擊OK,以下圖所示:
能夠看到讀取到了RtuSlaveTest程序中模擬的3個寄存器的數據,注意別忘了先啓動RtuSlaveTest程序!
選擇不一樣的功能碼就能夠讀取不一樣的數據,01讀取線圈狀態,02讀取離散輸入狀態,03讀取保持寄存器,04讀取輸入寄存器。
實際開發中可能更多的是開發主站設備程序,RtuMasterTest代碼以下:
public class RtuMasterTest { public static void main(String[] args) throws Exception{ createRtuMaster(); } private static void createRtuMaster() throws Exception{ // 設置串口參數,串口是COM1,波特率是9600 SerialPortWrapperImpl wrapper = new SerialPortWrapperImpl("COM1", 9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE, 0, 0); ModbusFactory modbusFactory = new ModbusFactory(); ModbusMaster master = modbusFactory.createRtuMaster(wrapper); master.init(); // 從站設備ID是1 int slaveId = 1; // 讀取保持寄存器 readHoldingRegisters(master, slaveId, 0, 3); // 將地址爲0的保持寄存器數據修改成0 writeRegister(master, slaveId, 0, 0); // 再讀取保持寄存器 readHoldingRegisters(master, slaveId, 0, 3); } private static void readHoldingRegisters(ModbusMaster master, int slaveId, int start, int len) throws Exception{ ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, start, len); ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request); if (response.isException()){ System.out.println("讀取保持寄存器錯誤,錯誤信息是" + response.getExceptionMessage()); }else { System.out.println("讀取保持寄存器=" + Arrays.toString(response.getShortData())); } } private static void writeRegister(ModbusMaster master, int slaveId, int offset, int value) throws Exception{ WriteRegisterRequest request = new WriteRegisterRequest(slaveId, offset, value); WriteRegisterResponse response = (WriteRegisterResponse) master.send(request); if (response.isException()){ System.out.println("寫保持寄存器錯誤,錯誤信息是" + response.getExceptionMessage()); }else{ System.out.println("寫保持寄存器成功"); } } }
先啓動RtuSlaveTest從站設備模擬程序,再啓動RtuMasterTest主站設備模擬程序,能夠看到雙方控制檯均有預期輸出,RtuMasterTest可以讀寫RtuSlaveTest中的數據。
一、初探ModBus4j簡單使用指南
二、使用java的modbus4j的Rtu方式獲取監測數據
三、Modbus java slave app