新公司新需求
各位老铁好! 时隔多日,又来更新了,新公司搞起工业物联网了。。。
需求:让主程序输出modbus通信报文
需求解析:
一开始以为就是打日志,直到同事给了一段报文
Tx:83661-15:21:03.390-00 0F 00 00 00 06 01 01 02 58 00 12 Rx:83662-15:21:03.442-00 0F 00 00 00 06 01 01 03 00 00 00
???
需求落地:
了解到代码底层和modbus通信用的tcp方式,modbus4j这个包,github可以搜到,第701星是我给的
好了,不bb,show code
com.serotonin.modbus4j.ip.tcp.TcpSlave:
/*
/*
* ============================================================================
* GNU General Public License
* ============================================================================
*
* Copyright (C) 2006-2011 Serotonin Software Technologies Inc. http://serotoninsoftware.com
* @author Matthew Lohbihler
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.serotonin.modbus4j.ip.tcp;
import com.serotonin.modbus4j.ModbusSlaveSet;
import com.serotonin.modbus4j.base.BaseMessageParser;
import com.serotonin.modbus4j.base.BaseRequestHandler;
import com.serotonin.modbus4j.base.ModbusUtils;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.ip.encap.EncapMessageParser;
import com.serotonin.modbus4j.ip.encap.EncapRequestHandler;
import com.serotonin.modbus4j.ip.xa.XaMessageParser;
import com.serotonin.modbus4j.ip.xa.XaRequestHandler;
import com.serotonin.modbus4j.sero.log.IOLog;
import com.serotonin.modbus4j.sero.messaging.MessageControl;
import com.serotonin.modbus4j.sero.messaging.TestableTransport;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* <p>TcpSlave class.</p>
*
* @author Matthew Lohbihler
* @version 5.0.0
*/
public class TcpSlave extends ModbusSlaveSet {
// Configuration fields
private final int port;
final String logPath;
final boolean encapsulated;
// Runtime fields.
private ServerSocket serverSocket;
final ExecutorService executorService;
final List<TcpConnectionHandler> listConnections = new ArrayList<>();
/**
* <p>Constructor for TcpSlave.</p>
*
* @param encapsulated a boolean.
*/
public TcpSlave(boolean encapsulated) {
this(ModbusUtils.TCP_PORT, null, encapsulated);
}
/**
* <p>Constructor for TcpSlave.</p>
*
* @param port a int.
* @param encapsulated a boolean.
*/
public TcpSlave(int port, String logPath, boolean encapsulated) {
this.port = port;
this.logPath = logPath;
this.encapsulated = encapsulated;
executorService = Executors.newCachedThreadPool();
}
/** {@inheritDoc} */
@Override
public void start() throws ModbusInitException {
try {
serverSocket = new ServerSocket(port);
Socket socket;
while (true) {
socket = serverSocket.accept();
TcpConnectionHandler handler = new TcpConnectionHandler(socket, logPath);
executorService.execute(handler);
synchronized (listConnections) {
listConnections.add(handler);
}
}
}
catch (IOException e) {
throw new ModbusInitException(e);
}
}
/** {@inheritDoc} */
@Override
public void stop() {
// Close the socket first to prevent new messages.
try {
serverSocket.close();
}
catch (IOException e) {
getExceptionHandler().receivedException(e);
}
// Close all open connections.
synchronized (listConnections) {
for (TcpConnectionHandler tch : listConnections)
tch.kill();
listConnections.clear();
}
// Now close the executor service.
executorService.shutdown();
try {
executorService.awaitTermination(3, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
getExceptionHandler().receivedException(e);
}
}
class TcpConnectionHandler implements Runnable {
private final Socket socket;
private TestableTransport transport;
private MessageControl conn;
private String logPath;
TcpConnectionHandler(Socket socket, String logPath) throws ModbusInitException {
this.socket = socket;
this.logPath = logPath;
try {
transport = new TestableTransport(socket.getInputStream(), socket.getOutputStream());
}
catch (IOException e) {
throw new ModbusInitException(e);
}
}
@Override
public void run() {
BaseMessageParser messageParser;
BaseRequestHandler requestHandler;
if (encapsulated) {
messageParser = new EncapMessageParser(false);
requestHandler = new EncapRequestHandler(TcpSlave.this);
}
else {
messageParser = new XaMessageParser(false);
requestHandler = new XaRequestHandler(TcpSlave.this);
}
conn = new MessageControl();
if(null != logPath) {
conn.setIoLog(new IOLog(logPath));
}
conn.setExceptionHandler(getExceptionHandler());
try {
conn.start(transport, messageParser, requestHandler, null);
executorService.execute(transport);
}
catch (IOException e) {
getExceptionHandler().receivedException(new ModbusInitException(e));
}
// Monitor the socket to detect when it gets closed.
while (true) {
try {
transport.testInputStream();
}
catch (IOException e) {
break;
}
try {
Thread.sleep(500);
}
catch (InterruptedException e) {
// no op
}
}
conn.close();
kill();
synchronized (listConnections) {
listConnections.remove(this);
}
}
void kill() {
try {
socket.close();
}
catch (IOException e) {
getExceptionHandler().receivedException(new ModbusInitException(e));
}
}
}
}
com/serotonin/modbus4j/sero/messaging/MessageControl.java
package com.serotonin.modbus4j.sero.messaging;
import java.io.IOException;
import com.serotonin.modbus4j.sero.io.StreamUtils;
import com.serotonin.modbus4j.sero.log.BaseIOLog;
import com.serotonin.modbus4j.sero.timer.SystemTimeSource;
import com.serotonin.modbus4j.sero.timer.TimeSource;
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
/**
* In general there are three messaging activities:
* <ol>
* <li>Send a message for which no reply is expected, e.g. a broadcast.</li>
* <li>Send a message and wait for a response with timeout and retries.</li>
* <li>Listen for unsolicited requests.</li>
* </ol>
*
* @author Matthew Lohbihler
* @version 5.0.0
*/
public class MessageControl implements DataConsumer {
private static int DEFAULT_RETRIES = 2;
private static int DEFAULT_TIMEOUT = 500;
public boolean DEBUG = false;
private Transport transport;
private MessageParser messageParser;
private RequestHandler requestHandler;
private WaitingRoomKeyFactory waitingRoomKeyFactory;
private MessagingExceptionHandler exceptionHandler = new DefaultMessagingExceptionHandler();
private int retries = DEFAULT_RETRIES;
private int timeout = DEFAULT_TIMEOUT;
private int discardDataDelay = 0;
private long lastDataTimestamp;
private BaseIOLog ioLog;
private TimeSource timeSource = new SystemTimeSource();
private final WaitingRoom waitingRoom = new WaitingRoom();
private final ByteQueue dataBuffer = new ByteQueue();
/**
* <p>start.</p>
*
* @param transport a {@link com.serotonin.modbus4j.sero.messaging.Transport} object.
* @param messageParser a {@link com.serotonin.modbus4j.sero.messaging.MessageParser} object.
* @param handler a {@link com.serotonin.modbus4j.sero.messaging.RequestHandler} object.
* @param waitingRoomKeyFactory a {@link com.serotonin.modbus4j.sero.messaging.WaitingRoomKeyFactory} object.
* @throws java.io.IOException if any.
*/
public void start(Transport transport, MessageParser messageParser, RequestHandler handler,
WaitingRoomKeyFactory waitingRoomKeyFactory) throws IOException {
this.transport = transport;
this.messageParser = messageParser;
this.requestHandler = handler;
this.waitingRoomKeyFactory = waitingRoomKeyFactory;
waitingRoom.setKeyFactory(waitingRoomKeyFactory);
transport.setConsumer(this);
}
/**
* <p>close.</p>
*/
public void close() {
transport.removeConsumer();
}
/**
* <p>Setter for the field <code>exceptionHandler</code>.</p>
*
* @param exceptionHandler a {@link com.serotonin.modbus4j.sero.messaging.MessagingExceptionHandler} object.
*/
public void setExceptionHandler(MessagingExceptionHandler exceptionHandler) {
if (exceptionHandler == null)
this.exceptionHandler = new DefaultMessagingExceptionHandler();
else
this.exceptionHandler = exceptionHandler;
}
/**
* <p>Getter for the field <code>retries</code>.</p>
*
* @return a int.
*/
public int getRetries() {
return retries;
}
/**
* <p>Setter for the field <code>retries</code>.</p>
*
* @param retries a int.
*/
public void setRetries(int retries) {
this.retries = retries;
}
/**
* <p>Getter for the field <code>timeout</code>.</p>
*
* @return a int.
*/
public int getTimeout() {
return timeout;
}
/**
* <p>Setter for the field <code>timeout</code>.</p>
*
* @param timeout a int.
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
/**
* <p>Getter for the field <code>discardDataDelay</code>.</p>
*
* @return a int.
*/
public int getDiscardDataDelay() {
return discardDataDelay;
}
/**
* <p>Setter for the field <code>discardDataDelay</code>.</p>
*
* @param discardDataDelay a int.
*/
public void setDiscardDataDelay(int discardDataDelay) {
this.discardDataDelay = discardDataDelay;
}
/**
* <p>Getter for the field <code>ioLog</code>.</p>
*
* @return a {@link com.serotonin.modbus4j.sero.log.BaseIOLog} object.
*/
public BaseIOLog getIoLog() {
return ioLog;
}
/**
* <p>Setter for the field <code>ioLog</code>.</p>
*
* @param ioLog a {@link com.serotonin.modbus4j.sero.log.BaseIOLog} object.
*/
public void setIoLog(BaseIOLog ioLog) {
this.ioLog = ioLog;
}
/**
* <p>Getter for the field <code>timeSource</code>.</p>
*
* @return a {@link com.serotonin.modbus4j.sero.timer.TimeSource} object.
*/
public TimeSource getTimeSource() {
return timeSource;
}
/**
* <p>Setter for the field <code>timeSource</code>.</p>
*
* @param timeSource a {@link com.serotonin.modbus4j.sero.timer.TimeSource} object.
*/
public void setTimeSource(TimeSource timeSource) {
this.timeSource = timeSource;
}
/**
* <p>send.</p>
*
* @param request a {@link com.serotonin.modbus4j.sero.messaging.OutgoingRequestMessage} object.
* @return a {@link com.serotonin.modbus4j.sero.messaging.IncomingResponseMessage} object.
* @throws java.io.IOException if any.
*/
public IncomingResponseMessage send(OutgoingRequestMessage request) throws IOException {
return send(request, timeout, retries);
}
/**
* <p>send.</p>
*
* @param request a {@link com.serotonin.modbus4j.sero.messaging.OutgoingRequestMessage} object.
* @param timeout a int.
* @param retries a int.
* @return a {@link com.serotonin.modbus4j.sero.messaging.IncomingResponseMessage} object.
* @throws java.io.IOException if any.
*/
public IncomingResponseMessage send(OutgoingRequestMessage request, int timeout, int retries) throws IOException {
byte[] data = request.getMessageData();
if (DEBUG)
System.out.println("MessagingControl.send: " + StreamUtils.dumpHex(data));
IncomingResponseMessage response = null;
if (request.expectsResponse()) {
WaitingRoomKey key = waitingRoomKeyFactory.createWaitingRoomKey(request);
// Enter the waiting room
waitingRoom.enter(key);
try {
do {
// Send the request.
write(data);
// Wait for the response.
response = waitingRoom.getResponse(key, timeout);
if (DEBUG && response == null)
System.out.println("Timeout waiting for response");
}
while (response == null && retries-- > 0);
}
finally {
// Leave the waiting room.
waitingRoom.leave(key);
}
if (response == null)
throw new TimeoutException("request=" + request);
}
else
write(data);
return response;
}
/**
* <p>send.</p>
*
* @param response a {@link com.serotonin.modbus4j.sero.messaging.OutgoingResponseMessage} object.
* @throws java.io.IOException if any.
*/
public void send(OutgoingResponseMessage response) throws IOException {
write(response.getMessageData());
}
/**
* {@inheritDoc}
*
* Incoming data from the transport. Single-threaded.
*/
public void data(byte[] b, int len) {
if (DEBUG)
System.out.println("MessagingConnection.read: " + StreamUtils.dumpHex(b, 0, len));
if (ioLog != null)
ioLog.input(b, 0, len);
if (discardDataDelay > 0) {
long now = timeSource.currentTimeMillis();
if (now - lastDataTimestamp > discardDataDelay)
dataBuffer.clear();
lastDataTimestamp = now;
}
dataBuffer.push(b, 0, len);
// There may be multiple messages in the data, so enter a loop.
while (true) {
// Attempt to parse a message.
try {
// Mark where we are in the buffer. The entire message may not be in yet, but since the parser
// will consume the buffer we need to be able to backtrack.
dataBuffer.mark();
IncomingMessage message = messageParser.parseMessage(dataBuffer);
if (message == null) {
// Nothing to do. Reset the buffer and exit the loop.
dataBuffer.reset();
break;
}
if (message instanceof IncomingRequestMessage) {
// Received a request. Give it to the request handler
if (requestHandler != null) {
OutgoingResponseMessage response = requestHandler
.handleRequest((IncomingRequestMessage) message);
if (response != null)
send(response);
}
}
else
// Must be a response. Give it to the waiting room.
waitingRoom.response((IncomingResponseMessage) message);
}
catch (Exception e) {
exceptionHandler.receivedException(e);
// Clear the buffer
// dataBuffer.clear();
}
}
}
private void write(byte[] data) throws IOException {
if (ioLog != null)
ioLog.output(data);
synchronized (transport) {
transport.write(data);
}
}
/** {@inheritDoc} */
public void handleIOException(IOException e) {
exceptionHandler.receivedException(e);
}
}
com/serotonin/modbus4j/sero/log/BaseIOLog.java
/**
* Copyright (C) 2014 Infinite Automation Software and Serotonin Software. All rights reserved.
* @author Terry Packer, Matthew Lohbihler
*/
package com.serotonin.modbus4j.sero.log;
import com.serotonin.modbus4j.sero.io.NullWriter;
import com.serotonin.modbus4j.sero.io.StreamUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* <p>Abstract BaseIOLog class.</p>
*
* @author Terry Packer
* @version 5.0.0
*/
public abstract class BaseIOLog {
private static final Log LOG = LogFactory.getLog(BaseIOLog.class);
/** Constant <code>DATE_FORMAT="yyyy/MM/dd-HH:mm:ss,SSS"</code> */
protected static final String DATE_FORMAT = "yyyy/MM/dd-HH:mm:ss,SSS";
protected final SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
protected PrintWriter out;
protected final File file;
protected final StringBuilder sb = new StringBuilder();
protected final Date date = new Date();
/**
* <p>Constructor for BaseIOLog.</p>
*
* @param logFile a {@link java.io.File} object.
*/
public BaseIOLog(File logFile){
this.file = logFile;
createOut();
}
/**
* Create the Print Writer output
*/
protected void createOut() {
try {
out = new PrintWriter(new FileWriter(file, true));
}
catch (IOException e) {
out = new PrintWriter(new NullWriter());
LOG.error("Error while creating process log", e);
}
}
/**
* <p>close.</p>
*/
public void close() {
out.close();
}
/**
* <p>input.</p>
*
* @param b an array of {@link byte} objects.
*/
public void input(byte[] b) {
log(true, b, 0, b.length);
}
/**
* <p>input.</p>
*
* @param b an array of {@link byte} objects.
* @param pos a int.
* @param len a int.
*/
public void input(byte[] b, int pos, int len) {
log(true, b, pos, len);
}
/**
* <p>output.</p>
*
* @param b an array of {@link byte} objects.
*/
public void output(byte[] b) {
log(false, b, 0, b.length);
}
/**
* <p>output.</p>
*
* @param b an array of {@link byte} objects.
* @param pos a int.
* @param len a int.
*/
public void output(byte[] b, int pos, int len) {
log(false, b, pos, len);
}
/**
* <p>log.</p>
*
* @param input a boolean.
* @param b an array of {@link byte} objects.
*/
public void log(boolean input, byte[] b) {
log(input, b, 0, b.length);
}
/**
* <p>log.</p>
*
* @param input a boolean.
* @param b an array of {@link byte} objects.
* @param pos a int.
* @param len a int.
*/
public synchronized void log(boolean input, byte[] b, int pos, int len) {
sizeCheck();
sb.delete(0, sb.length());
date.setTime(System.currentTimeMillis());
sb.append(input ? "Tx:" : "Rx:");
sb.append(sdf.format(date)).append("-");
sb.append(StreamUtils.dumpHex(b, pos, len));
out.println(sb.toString());
out.flush();
}
/**
* <p>log.</p>
*
* @param message a {@link java.lang.String} object.
*/
public synchronized void log(String message) {
sizeCheck();
sb.delete(0, sb.length());
date.setTime(System.currentTimeMillis());
sb.append(sdf.format(date)).append(" ");
sb.append(message);
out.println(sb.toString());
out.flush();
}
/**
* Check the size of the logfile and perform adjustments
* as necessary
*/
protected abstract void sizeCheck();
}
好了。。以上基本是需改内容
看使用
package com.serotonin.modbus4j.test;
import com.serotonin.modbus4j.BasicProcessImage;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusSlaveSet;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.code.RegisterRange;
import com.serotonin.modbus4j.exception.ModbusInitException;
import java.util.Random;
public class ListenerTest2 {
static Random random = new Random();
static float ir1Value = -100;
public static void main(String[] args) throws Exception {
ModbusFactory modbusFactory = new ModbusFactory();
final ModbusSlaveSet listener = modbusFactory.createTcpSlave(false);
listener.addProcessImage(getModscanProcessImage(1));
listener.addProcessImage(getModscanProcessImage(2));
// When the "listener" is started it will use the current thread to run. So, if an exception is not thrown
// (and we hope it won't be), the method call will not return. Therefore, we start the listener in a separate
// thread so that we can use this thread to modify the values.
new Thread(new Runnable() {
public void run() {
try {
listener.start();
}
catch (ModbusInitException e) {
e.printStackTrace();
}
}
}).start();
while (true) {
updateProcessImage1((BasicProcessImage) listener.getProcessImage(1));
updateProcessImage2((BasicProcessImage) listener.getProcessImage(2));
synchronized (listener) {
listener.wait(5000);
}
}
}
static void updateProcessImage1(BasicProcessImage processImage) {
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 0, DataType.TWO_BYTE_INT_UNSIGNED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 2, DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 10, DataType.TWO_BYTE_INT_UNSIGNED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 12, DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 20, DataType.TWO_BYTE_INT_UNSIGNED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 22, DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED,
random.nextInt(10000));
}
static void updateProcessImage2(BasicProcessImage processImage) {
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 0, DataType.TWO_BYTE_INT_UNSIGNED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 3, DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 10, DataType.TWO_BYTE_INT_UNSIGNED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 12, DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 20, DataType.TWO_BYTE_INT_UNSIGNED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 22, DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 99, DataType.TWO_BYTE_INT_UNSIGNED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 100, DataType.TWO_BYTE_INT_UNSIGNED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 101, DataType.TWO_BYTE_INT_UNSIGNED,
random.nextInt(10000));
}
static BasicProcessImage getModscanProcessImage(int slaveId) {
BasicProcessImage processImage = new BasicProcessImage(slaveId);
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 0, DataType.TWO_BYTE_INT_UNSIGNED,
random.nextInt(10000));
processImage.setNumeric(RegisterRange.HOLDING_REGISTER, 3, DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED,
random.nextInt(10000));
processImage.setAllowInvalidAddress(true);
processImage.setInvalidAddressValue(Short.MIN_VALUE);
processImage.setExceptionStatus((byte) 151);
return processImage;
}
}
本代码已开源,欢迎点星!多多交流,感谢感谢!