diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/exception/ModbusSlaveErrorResponseException.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/exception/ModbusSlaveErrorResponseException.java index 45e11611d2..d581a51abe 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/exception/ModbusSlaveErrorResponseException.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/exception/ModbusSlaveErrorResponseException.java @@ -12,6 +12,10 @@ */ package org.openhab.core.io.transport.modbus.exception; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + import org.eclipse.jdt.annotation.NonNullByDefault; /** @@ -24,78 +28,130 @@ import org.eclipse.jdt.annotation.NonNullByDefault; @NonNullByDefault public abstract class ModbusSlaveErrorResponseException extends ModbusTransportException { + private static final Map exceptionCodesIndex = new HashMap<>(10); + static { + for (KnownExceptionCode code : KnownExceptionCode.values()) { + exceptionCodesIndex.put(code.exceptionCode, code); + } + } + + /** + * Enum for known exception codes that modbus slaves (servers) can used to indicate exceptions + * + * @author Sami Salonen - Initial contribution + * + */ + public static enum KnownExceptionCode { + ILLEGAL_FUNCTION(1), + ILLEGAL_DATA_ACCESS(2), + ILLEGAL_DATA_VALUE(3), + SLAVE_DEVICE_FAILURE(4), + ACKNOWLEDGE(5), + SLAVE_DEVICE_BUSY(6), + NEGATIVE_ACKNOWLEDGE(7), + MEMORY_PARITY_ERROR(8), + GATEWAY_PATH_UNVAVAILABLE(10), + GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND(11); + + private final int exceptionCode; + + private KnownExceptionCode(int exceptionCode) { + this.exceptionCode = exceptionCode; + } + + public int getExceptionCode() { + return exceptionCode; + } + + public static Optional tryFromExceptionCode(int exceptionCode) { + return Optional.ofNullable(exceptionCodesIndex.get(exceptionCode)); + } + } + /** * The function code received in the query is not an allowable action for the slave. This may be because the - * function code is only applicable to newer devices, and was not implemented in the unit selected. It could also + * function code is only applicable to newer devices, and was not implemented in the unit selected. It could + * also * indicate that the slave is in the wrong state to process a request of this type, for example because it is - * unconfigured and is being asked to return register values. If a Poll Program Complete command was issued, this + * unconfigured and is being asked to return register values. If a Poll Program Complete command was issued, + * this * code indicates that no program function preceded it. */ - public static final int ILLEGAL_FUNCTION = 1; + public static final int ILLEGAL_FUNCTION = KnownExceptionCode.ILLEGAL_FUNCTION.getExceptionCode(); /** * The data address received in the query is not an allowable address for the slave. More specifically, the - * combination of reference number and transfer length is invalid. For a controller with 100 registers, a request + * combination of reference number and transfer length is invalid. For a controller with 100 registers, a + * request * with offset 96 and length 4 would succeed, a request with offset 96 and length 5 will generate exception 02. */ - public static final int ILLEGAL_DATA_ACCESS = 2; + public static final int ILLEGAL_DATA_ACCESS = KnownExceptionCode.ILLEGAL_DATA_ACCESS.getExceptionCode(); /** - * A value contained in the query data field is not an allowable value for the slave. This indicates a fault in the - * structure of remainder of a complex request, such as that the implied length is incorrect. It specifically does + * A value contained in the query data field is not an allowable value for the slave. This indicates a fault in + * the + * structure of remainder of a complex request, such as that the implied length is incorrect. It specifically + * does * NOT mean that a data item submitted for storage in a register has a value outside the expectation of the * application program, since the Modbus protocol is unaware of the significance of any particular value of any * particular register. */ - public static final int ILLEGAL_DATA_VALUE = 3; + public static final int ILLEGAL_DATA_VALUE = KnownExceptionCode.ILLEGAL_DATA_VALUE.getExceptionCode(); /** * An unrecoverable error occurred while the slave was attempting to perform the requested action. */ - public static final int SLAVE_DEVICE_FAILURE = 4; + public static final int SLAVE_DEVICE_FAILURE = KnownExceptionCode.SLAVE_DEVICE_FAILURE.getExceptionCode(); /** * Specialized use in conjunction with programming commands. - * The slave has accepted the request and is processing it, but a long duration of time will be required to do so. - * This response is returned to prevent a timeout error from occurring in the master. The master can next issue a + * The slave has accepted the request and is processing it, but a long duration of time will be required to do + * so. + * This response is returned to prevent a timeout error from occurring in the master. The master can next issue + * a * Poll Program Complete message to determine if processing is completed. */ - public static final int ACKNOWLEDGE = 5; + public static final int ACKNOWLEDGE = KnownExceptionCode.ACKNOWLEDGE.getExceptionCode(); /** * Specialized use in conjunction with programming commands. * The slave is engaged in processing a long-duration program command. The master should retransmit the message * later when the slave is free. */ - public static final int SLAVE_DEVICE_BUSY = 6; + public static final int SLAVE_DEVICE_BUSY = KnownExceptionCode.SLAVE_DEVICE_BUSY.getExceptionCode(); /** - * The slave cannot perform the program function received in the query. This code is returned for an unsuccessful + * The slave cannot perform the program function received in the query. This code is returned for an + * unsuccessful * programming request using function code 13 or 14 decimal. The master should request diagnostic or error * information from the slave. */ - public static final int NEGATIVE_ACKNOWLEDGE = 7; + public static final int NEGATIVE_ACKNOWLEDGE = KnownExceptionCode.ACKNOWLEDGE.getExceptionCode(); /** - * Specialized use in conjunction with function codes 20 and 21 and reference type 6, to indicate that the extended + * Specialized use in conjunction with function codes 20 and 21 and reference type 6, to indicate that the + * extended * file area failed to pass a consistency check. - * The slave attempted to read extended memory or record file, but detected a parity error in memory. The master can + * The slave attempted to read extended memory or record file, but detected a parity error in memory. The master + * can * retry the request, but service may be required on the slave device. */ - public static final int MEMORY_PARITY_ERROR = 8; + public static final int MEMORY_PARITY_ERROR = KnownExceptionCode.MEMORY_PARITY_ERROR.getExceptionCode(); /** * Specialized use in conjunction with gateways, indicates that the gateway was unable to allocate an internal - * communication path from the input port to the output port for processing the request. Usually means the gateway + * communication path from the input port to the output port for processing the request. Usually means the + * gateway * is misconfigured or overloaded. */ - public static final int GATEWAY_PATH_UNVAVAILABLE = 10; + public static final int GATEWAY_PATH_UNVAVAILABLE = KnownExceptionCode.GATEWAY_PATH_UNVAVAILABLE.getExceptionCode(); /** * Specialized use in conjunction with gateways, indicates that no response was obtained from the target device. * Usually means that the device is not present on the network. */ - public static final int GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 11; + public static final int GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = KnownExceptionCode.GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND + .getExceptionCode(); private static final long serialVersionUID = -1435199498550990487L; diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusManagerImpl.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusManagerImpl.java index 914579a301..0ab9971504 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusManagerImpl.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusManagerImpl.java @@ -524,7 +524,7 @@ public class ModbusManagerImpl implements ModbusManager { * With some connection types, the connection is reseted (disconnected), and new connection is received from the * pool. This means that potentially other operations queuing for the connection can be executed in-between. * - * With some other connection types, the operation is retried without reseting the connection type. + * With some other connection types, the operation is retried without resetting the connection type. * * @param task * @param oneOffTask @@ -620,11 +620,11 @@ public class ModbusManagerImpl implements ModbusManager { // broken pipe on write) if (willRetry) { logger.warn( - "Try {} out of {} failed when executing request ({}). Will try again soon. Error was I/O error, so reseting the connection. Error details: {} {} [operation ID {}]", + "Try {} out of {} failed when executing request ({}). Will try again soon. Error was I/O error, so resetting the connection. Error details: {} {} [operation ID {}]", tryIndex, maxTries, request, e.getClass().getName(), e.getMessage(), operationId); } else { logger.error( - "Last try {} failed when executing request ({}). Aborting. Error was I/O error, so reseting the connection. Error details: {} {} [operation ID {}]", + "Last try {} failed when executing request ({}). Aborting. Error was I/O error, so resetting the connection. Error details: {} {} [operation ID {}]", tryIndex, request, e.getClass().getName(), e.getMessage(), operationId); } if (endpoint instanceof ModbusSerialSlaveEndpoint) { @@ -643,11 +643,11 @@ public class ModbusManagerImpl implements ModbusManager { // broken pipe on write) if (willRetry) { logger.warn( - "Try {} out of {} failed when executing request ({}). Will try again soon. Error was I/O error, so reseting the connection. Error details: {} {} [operation ID {}]", + "Try {} out of {} failed when executing request ({}). Will try again soon. Error was I/O error, so resetting the connection. Error details: {} {} [operation ID {}]", tryIndex, maxTries, request, e.getClass().getName(), e.getMessage(), operationId); } else { logger.error( - "Last try {} failed when executing request ({}). Aborting. Error was I/O error, so reseting the connection. Error details: {} {} [operation ID {}]", + "Last try {} failed when executing request ({}). Aborting. Error was I/O error, so resetting the connection. Error details: {} {} [operation ID {}]", tryIndex, request, e.getClass().getName(), e.getMessage(), operationId); } if (endpoint instanceof ModbusSerialSlaveEndpoint) { @@ -679,11 +679,11 @@ public class ModbusManagerImpl implements ModbusManager { // transaction error details already logged if (willRetry) { logger.warn( - "Try {} out of {} failed when executing request ({}). Will try again soon. The response did not match the request. Reseting the connection. Error details: {} {} [operation ID {}]", + "Try {} out of {} failed when executing request ({}). Will try again soon. The response did not match the request. Resetting the connection. Error details: {} {} [operation ID {}]", tryIndex, maxTries, request, e.getClass().getName(), e.getMessage(), operationId); } else { logger.error( - "Last try {} failed when executing request ({}). Aborting. The response did not match the request. Reseting the connection. Error details: {} {} [operation ID {}]", + "Last try {} failed when executing request ({}). Aborting. The response did not match the request. Resetting the connection. Error details: {} {} [operation ID {}]", tryIndex, request, e.getClass().getName(), e.getMessage(), operationId); } if (endpoint instanceof ModbusSerialSlaveEndpoint) { @@ -701,11 +701,11 @@ public class ModbusManagerImpl implements ModbusManager { // Some other (unexpected) exception occurred if (willRetry) { logger.warn( - "Try {} out of {} failed when executing request ({}). Will try again soon. Error was unexpected error, so reseting the connection. Error details: {} {} [operation ID {}]", + "Try {} out of {} failed when executing request ({}). Will try again soon. Error was unexpected error, so resetting the connection. Error details: {} {} [operation ID {}]", tryIndex, maxTries, request, e.getClass().getName(), e.getMessage(), operationId, e); } else { logger.error( - "Last try {} failed when executing request ({}). Aborting. Error was unexpected error, so reseting the connection. Error details: {} {} [operation ID {}]", + "Last try {} failed when executing request ({}). Aborting. Error was unexpected error, so resetting the connection. Error details: {} {} [operation ID {}]", tryIndex, request, e.getClass().getName(), e.getMessage(), operationId, e); } // Invalidate connection, and empty (so that new connection is acquired before new retry) diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusSlaveErrorResponseExceptionImpl.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusSlaveErrorResponseExceptionImpl.java index ffe311002e..682f5eb517 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusSlaveErrorResponseExceptionImpl.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusSlaveErrorResponseExceptionImpl.java @@ -12,6 +12,8 @@ */ package org.openhab.core.io.transport.modbus.internal; +import java.util.Optional; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.io.transport.modbus.exception.ModbusSlaveErrorResponseException; @@ -29,10 +31,12 @@ import net.wimpi.modbus.ModbusSlaveException; public class ModbusSlaveErrorResponseExceptionImpl extends ModbusSlaveErrorResponseException { private static final long serialVersionUID = 6334580162425192133L; - private int type; + private int rawCode; + private Optional exceptionCode; public ModbusSlaveErrorResponseExceptionImpl(ModbusSlaveException e) { - type = e.getType(); + rawCode = e.getType(); + exceptionCode = KnownExceptionCode.tryFromExceptionCode(rawCode); } /** @@ -40,16 +44,17 @@ public class ModbusSlaveErrorResponseExceptionImpl extends ModbusSlaveErrorRespo */ @Override public int getExceptionCode() { - return type; + return rawCode; } @Override public @Nullable String getMessage() { - return String.format("Slave responsed with error=%d", type); + return String.format("Slave responded with error=%d (%s)", rawCode, + exceptionCode.map(c -> c.name()).orElse("unknown error code")); } @Override public String toString() { - return String.format("ModbusSlaveErrorResponseException(error=%d)", type); + return String.format("ModbusSlaveErrorResponseException(error=%d)", rawCode); } } diff --git a/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/ModbusSlaveErrorResponseExceptionImplTest.java b/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/ModbusSlaveErrorResponseExceptionImplTest.java new file mode 100644 index 0000000000..7879ce4de8 --- /dev/null +++ b/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/ModbusSlaveErrorResponseExceptionImplTest.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.transport.modbus.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.openhab.core.io.transport.modbus.internal.ModbusSlaveErrorResponseExceptionImpl; + +import net.wimpi.modbus.ModbusSlaveException; + +/** + * @author Sami Salonen - Initial contribution + */ +public class ModbusSlaveErrorResponseExceptionImplTest { + + @Test + public void testKnownCode1() { + assertEquals("Slave responded with error=1 (ILLEGAL_FUNCTION)", + new ModbusSlaveErrorResponseExceptionImpl(new ModbusSlaveException(1)).getMessage()); + } + + @Test + public void testKnownCode2() { + assertEquals("Slave responded with error=2 (ILLEGAL_DATA_ACCESS)", + new ModbusSlaveErrorResponseExceptionImpl(new ModbusSlaveException(2)).getMessage()); + } + + @Test + public void testUnknownCode() { + assertEquals("Slave responded with error=99 (unknown error code)", + new ModbusSlaveErrorResponseExceptionImpl(new ModbusSlaveException(99)).getMessage()); + } +}