Java,Springboot使用Modbus4J进行通信

/*
 * ============================================================================
 * 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>
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Boot是一个用于快速搭建基于Spring框架的应用程序的开发框架,而Modbus4j则是一个用于在Java应用程序中实现Modbus协议的库。 Modbus是一种用于不同设备之间通信通信协议,包括RTU(串行通信)和TCP(基于IP网络通信)。Modbus4j是一个为了简化在Java应用程序中使用Modbus协议进行通信开发的库。 在使用Spring Boot中集成Modbus4j RTU时,我们需要先引入Modbus4j的依赖,可以在项目的pom.xml文件中添加如下依赖: ```xml <dependency> <groupId>com.serotonin</groupId> <artifactId>modbus4j</artifactId> <version>3.0.2</version> </dependency> ``` 接下来,我们可以创建一个Modbus4j的配置类,并在其中进行相关配置。例如,指定Modbus RTU通信的串口及参数: ```java @Configuration public class ModbusConfig { @Bean public ModbusMaster modbusMaster() throws Exception { SerialParameters parameters = new SerialParameters(); parameters.setCommPortId("/dev/ttyUSB0"); // 串口号 parameters.setBaudRate(9600); // 波特率 parameters.setDataBits(8); // 数据位 parameters.setStopBits(1); // 停止位 parameters.setParity(Parity.NONE); // 校验位 ModbusMaster modbusMaster = new SerialModbusMaster(parameters); modbusMaster.init(); return modbusMaster; } } ``` 在上述配置中,我们创建了一个ModbusMaster对象,并指定了串口号、波特率、数据位、停止位和校验位等参数。 在需要使用Modbus通信的业务逻辑中,我们可以通过注入ModbusMaster对象来进行通信操作。例如,读取Modbus设备的寄存器值: ```java @Service public class ModbusService { @Autowired private ModbusMaster modbusMaster; public int readRegister(int slaveId, int register) throws ModbusTransportException { ReadInputRegistersRequest request = new ReadInputRegistersRequest(slaveId, register, 1); ReadInputRegistersResponse response = (ReadInputRegistersResponse) modbusMaster.send(request); return response.getRegisters()[0].toShort(); } } ``` 上述代码中,我们通过注入ModbusMaster对象,并使用该对象发送读取寄存器请求,然后获取返回的寄存器值。 总之,通过在Spring Boot使用Modbus4j RTU,我们可以方便地实现与Modbus设备的通信,并轻松读写设备的寄存器等操作。以上只是一个简单的示例,实际使用中还可以进行更多的功能扩展和定制化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潘诺西亚的火山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值