/*
* ============================================================================
* 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;
import com.serotonin.modbus4j.code.RegisterRange;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.sero.messaging.DefaultMessagingExceptionHandler;
import com.serotonin.modbus4j.sero.messaging.MessagingExceptionHandler;
/**
* Base level for masters and slaves/listeners
*
* TODO: - handle echoing in RS485
*
* @author mlohbihler
* @version 5.0.0
*/
public class Modbus {
/** Constant <code>DEFAULT_MAX_READ_BIT_COUNT=2000</code> */
public static final int DEFAULT_MAX_READ_BIT_COUNT = 2000;
/** Constant <code>DEFAULT_MAX_READ_REGISTER_COUNT=125</code> */
public static final int DEFAULT_MAX_READ_REGISTER_COUNT = 125;
/** Constant <code>DEFAULT_MAX_WRITE_REGISTER_COUNT=120</code> */
public static final int DEFAULT_MAX_WRITE_REGISTER_COUNT = 120;
private MessagingExceptionHandler exceptionHandler = new DefaultMessagingExceptionHandler();
private int maxReadBitCount = DEFAULT_MAX_READ_BIT_COUNT;
private int maxReadRegisterCount = DEFAULT_MAX_READ_REGISTER_COUNT;
private int maxWriteRegisterCount = DEFAULT_MAX_WRITE_REGISTER_COUNT;
/**
* <p>getMaxReadCount.</p>
*
* @param registerRange a int.
* @return a int.
*/
public int getMaxReadCount(int registerRange) {
switch (registerRange) {
case RegisterRange.COIL_STATUS:
case RegisterRange.INPUT_STATUS:
return maxReadBitCount;
case RegisterRange.HOLDING_REGISTER:
case RegisterRange.INPUT_REGISTER:
return maxReadRegisterCount;
}
return -1;
}
/**
* <p>validateNumberOfBits.</p>
*
* @param bits a int.
* @throws com.serotonin.modbus4j.exception.ModbusTransportException if any.
*/
public void validateNumberOfBits(int bits) throws ModbusTransportException {
if (bits < 1 || bits > maxReadBitCount)
throw new ModbusTransportException("Invalid number of bits: " + bits);
}
/**
* <p>validateNumberOfRegisters.</p>
*
* @param registers a int.
* @throws com.serotonin.modbus4j.exception.ModbusTransportException if any.
*/
public void validateNumberOfRegisters(int registers) throws ModbusTransportException {
if (registers < 1 || registers > maxReadRegisterCount)
throw new ModbusTransportException("Invalid number of registers: " + registers);
}
/**
* <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>exceptionHandler</code>.</p>
*
* @return a {@link com.serotonin.modbus4j.sero.messaging.MessagingExceptionHandler} object.
*/
public MessagingExceptionHandler getExceptionHandler() {
return exceptionHandler;
}
/**
* <p>Getter for the field <code>maxReadBitCount</code>.</p>
*
* @return a int.
*/
public int getMaxReadBitCount() {
return maxReadBitCount;
}
/**
* <p>Setter for the field <code>maxReadBitCount</code>.</p>
*
* @param maxReadBitCount a int.
*/
public void setMaxReadBitCount(int maxReadBitCount) {
this.maxReadBitCount = maxReadBitCount;
}
/**
* <p>Getter for the field <code>maxReadRegisterCount</code>.</p>
*
* @return a int.
*/
public int getMaxReadRegisterCount() {
return maxReadRegisterCount;
}
/**
* <p>Setter for the field <code>maxReadRegisterCount</code>.</p>
*
* @param maxReadRegisterCount a int.
*/
public void setMaxReadRegisterCount(int maxReadRegisterCount) {
this.maxReadRegisterCount = maxReadRegisterCount;
}
/**
* <p>Getter for the field <code>maxWriteRegisterCount</code>.</p>
*
* @return a int.
*/
public int getMaxWriteRegisterCount() {
return maxWriteRegisterCount;
}
/**
* <p>Setter for the field <code>maxWriteRegisterCount</code>.</p>
*
* @param maxWriteRegisterCount a int.
*/
public void setMaxWriteRegisterCount(int maxWriteRegisterCount) {
this.maxWriteRegisterCount = maxWriteRegisterCount;
}
}
/*
* ============================================================================
* 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;
import com.serotonin.modbus4j.base.KeyedModbusLocator;
import com.serotonin.modbus4j.base.ReadFunctionGroup;
import com.serotonin.modbus4j.base.SlaveProfile;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.code.ExceptionCode;
import com.serotonin.modbus4j.code.FunctionCode;
import com.serotonin.modbus4j.code.RegisterRange;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.InvalidDataConversionException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.locator.BinaryLocator;
import com.serotonin.modbus4j.locator.NumericLocator;
import com.serotonin.modbus4j.msg.*;
import com.serotonin.modbus4j.sero.epoll.InputStreamEPollWrapper;
import com.serotonin.modbus4j.sero.log.BaseIOLog;
import com.serotonin.modbus4j.sero.messaging.MessageControl;
import com.serotonin.modbus4j.sero.util.ArrayUtils;
import com.serotonin.modbus4j.sero.util.ProgressiveTask;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <p>Abstract ModbusMaster class.</p>
*
* @author Matthew Lohbihler
* @version 5.0.0
*/
abstract public class ModbusMaster extends Modbus {
private int timeout = 500;
private int retries = 2;
/**
* Should we validate the responses:
* - ensure that the requested slave id is what is in the response
*/
protected boolean validateResponse;
/**
* If connection is established with slave/slaves
*/
protected boolean connected = false;
/**
* <p>isConnected.</p>
*
* @return a boolean.
*/
public boolean isConnected() {
return connected;
}
/**
* <p>Setter for the field <code>connected</code>.</p>
*
* @param connected a boolean.
*/
public void setConnected(boolean connected) {
this.connected = connected;
}
/**
* If the slave equipment only supports multiple write commands, set this to true. Otherwise, and combination of
* single or multiple write commands will be used as appropriate.
*/
private boolean multipleWritesOnly;
private int discardDataDelay = 0;
private BaseIOLog ioLog;
/**
* An input stream ePoll will use a single thread to read all input streams. If multiple serial or TCP modbus
* connections are to be made, an ePoll can be much more efficient.
*/
private InputStreamEPollWrapper ePoll;
private final Map<Integer, SlaveProfile> slaveProfiles = new HashMap<>();
protected boolean initialized;
/**
* <p>init.</p>
*
* @throws com.serotonin.modbus4j.exception.ModbusInitException if any.
*/
abstract public void init() throws ModbusInitException;
/**
* <p>isInitialized.</p>
*
* @return a boolean.
*/
public boolean isInitialized() {
return initialized;
}
/**
* <p>destroy.</p>
*/
abstract public void destroy();
/**
* <p>send.</p>
*
* @param request a {@link com.serotonin.modbus4j.msg.ModbusRequest} object.
* @return a {@link com.serotonin.modbus4j.msg.ModbusResponse} object.
* @throws com.serotonin.modbus4j.exception.ModbusTransportException if any.
*/
public final ModbusResponse send(ModbusRequest request) throws ModbusTransportException {
request.validate(this);
ModbusResponse modbusResponse = sendImpl(request);
if(validateResponse)
modbusResponse.validateResponse(request);
return modbusResponse;
}
/**
* <p>sendImpl.</p>
*
* @param request a {@link com.serotonin.modbus4j.msg.ModbusRequest} object.
* @return a {@link com.serotonin.modbus4j.msg.ModbusResponse} object.
* @throws com.serotonin.modbus4j.exception.ModbusTransportException if any.
*/
abstract public ModbusResponse sendImpl(ModbusRequest request) throws ModbusTransportException;
/**
* Returns a value from the modbus network according to the given locator information. Various data types are
* allowed to be requested including multi-word types. The determination of the correct request message to send is
* handled automatically.
*
* @param locator
* the information required to locate the value in the modbus network.
* @return an object representing the value found. This will be one of Boolean, Short, Integer, Long, BigInteger,
* Float, or Double. See the DataType enumeration for details on which type to expect.
* @throws com.serotonin.modbus4j.exception.ModbusTransportException
* if there was an IO error or other technical failure while sending the message
* @throws com.serotonin.modbus4j.exception.ErrorResponseException
* if the response returned from the slave was an exception.
* @param <T> a T object.
*/
@SuppressWarnings("unchecked")
public <T> T getValue(BaseLocator<T> locator) throws ModbusTransportException, ErrorResponseException {
BatchRead<String> batch = new BatchRead<>();
batch.addLocator("", locator);
BatchResults<String> result = send(batch);
return (T) result.getValue("");
}
/**
* Sets the given value in the modbus network according to the given locator information. Various data types are
* allowed to be set including including multi-word types. The determination of the correct write message to send is
* handled automatically.
*
* @param locator
* the information required to locate the value in the modbus network.
* @param value an object representing the value to be set. This will be one of Boolean, Short, Integer, Long, BigInteger,
* Float, or Double. See the DataType enumeration for details on which type to expect.
* @throws com.serotonin.modbus4j.exception.ModbusTransportException
* if there was an IO error or other technical failure while sending the message
* @throws com.serotonin.modbus4j.exception.ErrorResponseException
* if the response returned from the slave was an exception.
* @param <T> type of locator
*/
public <T> void setValue(BaseLocator<T> locator, Object value) throws ModbusTransportException,
ErrorResponseException {
int slaveId = locator.getSlaveId();
int registerRange = locator.getRange();
int writeOffset = locator.getOffset();
// Determine the request type that we will use
if (registerRange == RegisterRange.INPUT_STATUS || registerRange == RegisterRange.INPUT_REGISTER)
throw new RuntimeException("Cannot write to input status or input register ranges");
if (registerRange == RegisterRange.COIL_STATUS) {
if (!(value instanceof Boolean))
throw new InvalidDataConversionException("Only boolean values can be written to coils");
if (multipleWritesOnly)
setValue(new WriteCoilsRequest(slaveId, writeOffset, new boolean[] { ((Boolean) value).booleanValue() }));
else
setValue(new WriteCoilRequest(slaveId, writeOffset, ((Boolean) value).booleanValue()));
}
else {
// Writing to holding registers.
if (locator.getDataType() == DataType.BINARY) {
if (!(value instanceof Boolean))
throw new InvalidDataConversionException("Only boolean values can be written to coils");
setHoldingRegisterBit(slaveId, writeOffset, ((BinaryLocator) locator).getBit(),
((Boolean) value).booleanValue());
}
else {
// Writing some kind of value to a holding register.
@SuppressWarnings("unchecked")
short[] data = locator.valueToShorts((T) value);
if (data.length == 1 && !multipleWritesOnly)
setValue(new WriteRegisterRequest(slaveId, writeOffset, data[0]));
else
setValue(new WriteRegistersRequest(slaveId, writeOffset, data));
}
}
}
/**
* Node scanning. Returns a list of slave nodes that respond to a read exception status request (perhaps with an
* error, but respond nonetheless).
*
* Note: a similar scan could be done for registers in nodes, but, for one thing, it would take some time to run,
* and in any case the results would not be meaningful since there would be no semantic information accompanying the
* results.
*
* @return a {@link List} object.
*/
public List<Integer> scanForSlaveNodes() {
List<Integer> result = new ArrayList<>();
for (int i = 1; i <= 240; i++) {
if (testSlaveNode(i))
result.add(i);
}
return result;
}
/**
* <p>scanForSlaveNodes.</p>
*
* @param l a {@link com.serotonin.modbus4j.NodeScanListener} object.
* @return a {@link com.serotonin.modbus4j.sero.util.ProgressiveTask} object.
*/
public ProgressiveTask scanForSlaveNodes(final NodeScanListener l) {
l.progressUpdate(0);
ProgressiveTask task = new ProgressiveTask(l) {
private int node = 1;
@Override
protected void runImpl() {
if (testSlaveNode(node))
l.nodeFound(node);
declareProgress(((float) node) / 240);
node++;
if (node > 240)
completed = true;
}
};
new Thread(task).start();
return task;
}
/**
* <p>testSlaveNode.</p>
*
* @param node a int.
* @return a boolean.
*/
public boolean testSlaveNode(int node) {
try {
send(new ReadHoldingRegistersRequest(node, 0, 1));
}
catch (ModbusTransportException e) {
// If there was a transport exception, there's no node there.
return false;
}
return true;
}
/**
* <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) {
if (retries < 0)
this.retries = 0;
else
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) {
if (timeout < 1)
this.timeout = 1;
else
this.timeout = timeout;
}
/**
* <p>isMultipleWritesOnly.</p>
*
* @return a boolean.
*/
public boolean isMultipleWritesOnly() {
return multipleWritesOnly;
}
/**
* <p>Setter for the field <code>multipleWritesOnly</code>.</p>
*
* @param multipleWritesOnly a boolean.
*/
public void setMultipleWritesOnly(boolean multipleWritesOnly) {
this.multipleWritesOnly = multipleWritesOnly;
}
/**
* <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) {
if (discardDataDelay < 0)
this.discardDataDelay = 0;
else
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>ePoll</code>.</p>
*
* @return a {@link com.serotonin.modbus4j.sero.epoll.InputStreamEPollWrapper} object.
*/
public InputStreamEPollWrapper getePoll() {
return ePoll;
}
/**
* <p>Setter for the field <code>ePoll</code>.</p>
*
* @param ePoll a {@link com.serotonin.modbus4j.sero.epoll.InputStreamEPollWrapper} object.
*/
public void setePoll(InputStreamEPollWrapper ePoll) {
this.ePoll = ePoll;
}
/**
* Useful for sending a number of polling commands at once, or at least in as optimal a batch as possible.
*
* @param batch a {@link com.serotonin.modbus4j.BatchRead} object.
* @return a {@link com.serotonin.modbus4j.BatchResults} object.
* @throws com.serotonin.modbus4j.exception.ModbusTransportException if any.
* @throws com.serotonin.modbus4j.exception.ErrorResponseException if any.
* @param <K> type of result
*/
public <K> BatchResults<K> send(BatchRead<K> batch) throws ModbusTransportException, ErrorResponseException {
if (!initialized)
throw new ModbusTransportException("not initialized");
BatchResults<K> results = new BatchResults<>();
List<ReadFunctionGroup<K>> functionGroups = batch.getReadFunctionGroups(this);
// Execute each read function and process the results.
for (ReadFunctionGroup<K> functionGroup : functionGroups) {
sendFunctionGroup(functionGroup, results, batch.isErrorsInResults(), batch.isExceptionsInResults());
if (batch.isCancel())
break;
}
return results;
}
//
//
// Protected methods
//
/**
* <p>getMessageControl.</p>
*
* @return a {@link com.serotonin.modbus4j.sero.messaging.MessageControl} object.
*/
protected MessageControl getMessageControl() {
MessageControl conn = new MessageControl();
conn.setRetries(getRetries());
conn.setTimeout(getTimeout());
conn.setDiscardDataDelay(getDiscardDataDelay());
conn.setExceptionHandler(getExceptionHandler());
conn.setIoLog(ioLog);
return conn;
}
/**
* <p>closeMessageControl.</p>
*
* @param conn a {@link com.serotonin.modbus4j.sero.messaging.MessageControl} object.
*/
protected void closeMessageControl(MessageControl conn) {
if (conn != null)
conn.close();
}
//
//
// Private stuff
//
/**
* This method assumes that all locators have already been pre-sorted and grouped into valid requests, say, by the
* createRequestGroups method.
*/
private <K> void sendFunctionGroup(ReadFunctionGroup<K> functionGroup, BatchResults<K> results,
boolean errorsInResults, boolean exceptionsInResults) throws ModbusTransportException,
ErrorResponseException {
int slaveId = functionGroup.getSlaveAndRange().getSlaveId();
int startOffset = functionGroup.getStartOffset();
int length = functionGroup.getLength();
// Inspect the function group for data required to create the request.
ModbusRequest request;
if (functionGroup.getFunctionCode() == FunctionCode.READ_COILS)
request = new ReadCoilsRequest(slaveId, startOffset, length);
else if (functionGroup.getFunctionCode() == FunctionCode.READ_DISCRETE_INPUTS)
request = new ReadDiscreteInputsRequest(slaveId, startOffset, length);
else if (functionGroup.getFunctionCode() == FunctionCode.READ_HOLDING_REGISTERS)
request = new ReadHoldingRegistersRequest(slaveId, startOffset, length);
else if (functionGroup.getFunctionCode() == FunctionCode.READ_INPUT_REGISTERS)
request = new ReadInputRegistersRequest(slaveId, startOffset, length);
else
throw new RuntimeException("Unsupported function");
ReadResponse response;
try {
response = (ReadResponse) send(request);
}
catch (ModbusTransportException e) {
if (!exceptionsInResults)
throw e;
for (KeyedModbusLocator<K> locator : functionGroup.getLocators())
results.addResult(locator.getKey(), e);
return;
}
byte[] data = null;
if (!errorsInResults && response.isException())
throw new ErrorResponseException(request, response);
else if (!response.isException())
data = response.getData();
for (KeyedModbusLocator<K> locator : functionGroup.getLocators()) {
if (errorsInResults && response.isException())
results.addResult(locator.getKey(), new ExceptionResult(response.getExceptionCode()));
else {
try {
results.addResult(locator.getKey(), locator.bytesToValue(data, startOffset));
}
catch (RuntimeException e) {
throw new RuntimeException("Result conversion exception. data=" + ArrayUtils.toHexString(data)
+ ", startOffset=" + startOffset + ", locator=" + locator + ", functionGroup.functionCode="
+ functionGroup.getFunctionCode() + ", functionGroup.startOffset=" + startOffset
+ ", functionGroup.length=" + length, e);
}
}
}
}
private void setValue(ModbusRequest request) throws ModbusTransportException, ErrorResponseException {
ModbusResponse response = send(request);
if (response == null)
// This should only happen if the request was a broadcast
return;
if (response.isException())
throw new ErrorResponseException(request, response);
}
private void setHoldingRegisterBit(int slaveId, int writeOffset, int bit, boolean value)
throws ModbusTransportException, ErrorResponseException {
// Writing a bit in a holding register field. There are two ways to do this. The easy way is to
// use a write mask request, but it is not always supported. The hard way is to read the value, change
// the appropriate bit, and then write it back again (so as not to overwrite the other bits in the
// value). However, since the hard way is not atomic, it is not fail-safe either, but it should be
// at least possible.
SlaveProfile sp = getSlaveProfile(slaveId);
if (sp.getWriteMaskRegister()) {
// Give the write mask a try.
WriteMaskRegisterRequest request = new WriteMaskRegisterRequest(slaveId, writeOffset);
request.setBit(bit, value);
ModbusResponse response = send(request);
if (response == null)
// This should only happen if the request was a broadcast
return;
if (!response.isException())
// Hey, cool, it worked.
return;
if (response.getExceptionCode() == ExceptionCode.ILLEGAL_FUNCTION)
// The function is probably not supported. Fail-over to the two step.
sp.setWriteMaskRegister(false);
else
throw new ErrorResponseException(request, response);
}
// Do it the hard way. Get the register's current value.
int regValue = (Integer) getValue(new NumericLocator(slaveId, RegisterRange.HOLDING_REGISTER, writeOffset,
DataType.TWO_BYTE_INT_UNSIGNED));
// Modify the value according to the given bit and value.
if (value)
regValue = regValue | 1 << bit;
else
regValue = regValue & ~(1 << bit);
// Write the new register value.
setValue(new WriteRegisterRequest(slaveId, writeOffset, regValue));
}
private SlaveProfile getSlaveProfile(int slaveId) {
SlaveProfile sp = slaveProfiles.get(slaveId);
if (sp == null) {
sp = new SlaveProfile();
slaveProfiles.put(slaveId, sp);
}
return sp;
}
}
工具类
package com.zg.DAL;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import java.util.List;
/**
* @Auther: zhao
* @Date: 2022/8/9 - 08 - 09 - 23:06
* @Description: com.zg.DAL
* @version: 1.0
*/
public interface IModbusHelper {
//void Conn(String Ip, Integer Port, List<Variable> variables);
void Conn(String Ip, Integer Port);
void ReadHOLD() throws ModbusInitException, ErrorResponseException, ModbusTransportException;
void Write(int offset, int value);
void Close();
}
package com.zg.DAL;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.code.RegisterRange;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.ip.encap.EncapMessageParser;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.locator.NumericLocator;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Auther: zhao
* @Date: 2022/8/9 - 08 - 09 - 22:40
* @Description: com.zg.DAL
* @version: 1.0
*/
@Service
public class ModbusHelper implements IModbusHelper {
private ModbusFactory factory = new ModbusFactory();
IpParameters params = new IpParameters();
ModbusMaster master;
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
public ModbusHelper() {
}
@Override
public void Conn(String Ip, Integer Port) {
params.setHost(Ip);
params.setPort(Port);
//this.variables = variables;
master = factory.createTcpMaster(params, true);
master.setTimeout(2000);
master.setRetries(0);
}
@Override
//读取40001区的数据
public void ReadHOLD() throws ModbusInitException {
master.init();
//开启多线程
newCachedThreadPool.execute(()->
{
//循环读取变量集合的值,并写入value
while (true)
{
for (int i = 0; i<ComMethod.variables.size();i++)
{
Number value = null;
try {
value = master.getValue(new NumericLocator(1, RegisterRange.HOLDING_REGISTER, i,
DataType.TWO_BYTE_INT_SIGNED));
} catch (ModbusTransportException e) {
e.printStackTrace();
} catch (ErrorResponseException e) {
e.printStackTrace();
}
SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
Date date = new Date(System.currentTimeMillis());
//将读取变量的时间点存入静态类 ComMethod.variables 中
ComMethod.variables.get(i).setDateTime(formatter.format(date));
//重要 读出变量的值
ComMethod.variables.get(i).setValue(value.toString());
// variables.get(i).setDateTime(formatter.format(date));
// variables.get(i).setValue(value.toString());
//System.out.println("点名为:"+variables.get(i).getDescribe()+",变量值为:"+value.toString()+",采集时间:"+variables.get(i).getDateTime());
}
//while有延时0.5秒
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
//写入数据,offset是便宜了,在这里指第三个变量
@Override
public void Write(int offset, int value) {
BaseLocator<Number> locator = BaseLocator.holdingRegister(1, offset, DataType.TWO_BYTE_INT_SIGNED);
try {
master.setValue(locator, value);
} catch (ModbusTransportException e) {
e.printStackTrace();
} catch (ErrorResponseException e) {
e.printStackTrace();
}
}
//关闭链接
@Override
public void Close() {
master.destroy();
}
}
package com.zg.controller;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zg.DAL.ComMethod;
import com.zg.DAL.IModbusHelper;
import com.zg.DAL.ModbusHelper;
import com.zg.DAL.Variable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.function.Function;
/**
* @Auther: zhao
* @Date: 2022/8/9 - 08 - 09 - 22:04
* @Description: com.zg.controller
* @version: 1.0
*/
@Controller
public class MyController {
@Autowired
ModbusHelper modbusHelper;
public MyController() {
//初始化一些变量,存入静态集合ComMethod.variables 中
Variable ushort1 = new Variable();
ushort1.setId(1);
ushort1.setDescribe("ushort1");
Variable ushort2 = new Variable();
ushort2.setId(2);
ushort2.setDescribe("ushort2");
Variable ushort3 = new Variable();
ushort3.setId(3);
ushort3.setDescribe("ushort3");
Variable ushort4 = new Variable();
ushort4.setId(4);
ushort4.setDescribe("ushort4");
Variable ushort5 = new Variable();
ushort5.setId(5);
ushort5.setDescribe("ushort5");
ComMethod.variables.add(ushort1);
ComMethod.variables.add(ushort2);
ComMethod.variables.add(ushort3);
ComMethod.variables.add(ushort4);
ComMethod.variables.add(ushort5);
}
//首页
@RequestMapping("/")
public String Protal() {
return "index";
}
//读取值
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() throws ModbusInitException {
/**
* ModelAndView有Model和View的功能
* Model主要用于向请求域共享数据
* View主要用于设置视图,实现页面跳转
*/
modbusHelper.Conn("127.0.0.1",502);
modbusHelper.ReadHOLD();
ModelAndView modelAndView = new ModelAndView();
//处理模型数据,即向请求域request共享数据
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
newCachedThreadPool.execute(()->
{
while (true)
{
modelAndView.addObject("ushort1", ComMethod.variables.get(0).getValue());
modelAndView.addObject("ushort2", ComMethod.variables.get(1).getValue());
modelAndView.addObject("ushort3", ComMethod.variables.get(2).getValue());
modelAndView.addObject("ushort4", ComMethod.variables.get(3).getValue());
modelAndView.addObject("ushort5", ComMethod.variables.get(4).getValue());
System.out.println("点名为:"+ComMethod.variables.get(1).getDescribe()+",变量值为:"+ComMethod.variables.get(1).getValue()+",采集时间:"+ComMethod.variables.get(1).getDateTime());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//设置视图名称
modelAndView.setViewName("index");
return modelAndView;
}
//使用Model向rquset 效果更好
@RequestMapping("/testModel")
public String testModel(Model model) throws ModbusInitException {
modbusHelper.Conn("127.0.0.1",502);
modbusHelper.ReadHOLD();
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
// ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
// ScheduledFuture<?> scheduleAtFixedRate = threadPoolTaskScheduler.scheduleAtFixedRate(()->{model.addAttribute("u1", ComMethod.variables.get(0).getValue());},1000);
newCachedThreadPool.execute(()->
{
while (true)
{
model.addAttribute("u1", ComMethod.variables.get(0).getValue());
model.addAttribute("u2", ComMethod.variables.get(1).getValue());
model.addAttribute("u3", ComMethod.variables.get(2).getValue());
model.addAttribute("u4", ComMethod.variables.get(3).getValue());
model.addAttribute("u5", ComMethod.variables.get(4).getValue());
//System.out.println("点名为:"+ComMethod.variables.get(1).getDescribe()+",变量值为:"+ComMethod.variables.get(1).getValue()+",采集时间:"+ComMethod.variables.get(1).getDateTime());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
return "index";
}
//修改值
@RequestMapping(value = "/param",method = RequestMethod.POST)
public String getParamByController(String offset, String value)
{
System.out.println("offset:"+offset+",value:"+value);
modbusHelper.Write(Integer.parseInt(offset),Integer.parseInt(value));
return "success";
}
}
前端测试
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>helloworld!!!</h1>
<hr>
<a th:href="@{/hello}">测试SpringMVC</a><br>
<a th:href="@{/testModelAndView}">测试ModelAndView</a>
<p th:text="ushort1的值为+${ushort1}"></p>
<p th:text="ushort2的值为+${ushort2}"></p>
<p th:text="ushort3的值为+${ushort3}"></p>
<p th:text="ushort4的值为+${ushort4}"></p>
<p th:text="ushort5的值为+${ushort5}"></p>
<a th:href="@{/testModel}">测试Model</a>
<p th:text="ushort1的值为+${u1}"></p>
<p th:text="ushort2的值为+${u2}"></p>
<p th:text="ushort3的值为+${u3}"></p>
<p th:text="ushort4的值为+${u4}"></p>
<p th:text="ushort5的值为+${u5}"></p>
<a th:href="@{/test/rest/admin/55}">测试restful占位符RequsetMapping</a><br>
<form th:action="@{/param}" method="post">
偏移量:<input type="text" name="offset"> <br>
设定值:<input type="text" name="value"> <br>
<input type="submit" value="提交"> <br>
</form>
<hr>
</body>
</html>