[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
Sami Salonen 2021-09-25 21:23:01 +03:00 committed by GitHub
parent 641b92324d
commit 33703532e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 140 additions and 35 deletions

View File

@ -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;

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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());
}
}