[modbus] Improved error messages, fixed typos (#2492)
When modbus slave responds with explicit error code, we communicate the user what it means. E.g. error code 2 refers to "Illegal Data Access". In addition, fixed some simple typos discussed in https://github.com/openhab/openhab-addons/issues/8973#issuecomment-922232756 Signed-off-by: Sami Salonen <ssalonen@gmail.com>pull/2499/head
parent
641b92324d
commit
33703532e8
|
@ -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<Integer, KnownExceptionCode> 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<KnownExceptionCode> 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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<KnownExceptionCode> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue