use of org.openhab.core.io.transport.modbus.ModbusFailureCallback in project openhab-addons by openhab.
the class ModbusPollerThingHandlerTest method getPollerFailureCallback.
public ModbusFailureCallback<ModbusReadRequestBlueprint> getPollerFailureCallback(ModbusPollerThingHandler handler) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Field callbackField = ModbusPollerThingHandler.class.getDeclaredField("callbackDelegator");
callbackField.setAccessible(true);
return (ModbusFailureCallback<ModbusReadRequestBlueprint>) callbackField.get(handler);
}
use of org.openhab.core.io.transport.modbus.ModbusFailureCallback in project openhab-addons by openhab.
the class ModbusPollerThingHandlerTest method testErrorPassedToChildDataThings.
@Test
public void testErrorPassedToChildDataThings() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
PollTask pollTask = Mockito.mock(PollTask.class);
doReturn(pollTask).when(comms).registerRegularPoll(notNull(), eq(150l), eq(0L), notNull(), notNull());
Configuration pollerConfig = new Configuration();
pollerConfig.put("refresh", 150L);
pollerConfig.put("start", 5);
pollerConfig.put("length", 13);
pollerConfig.put("type", "coil");
poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID()).build();
addThing(poller);
verifyEndpointBasicInitInteraction();
assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
final ArgumentCaptor<ModbusFailureCallback<ModbusReadRequestBlueprint>> callbackCapturer = ArgumentCaptor.forClass((Class) ModbusFailureCallback.class);
verify(comms).registerRegularPoll(any(), eq(150l), eq(0L), notNull(), callbackCapturer.capture());
ModbusFailureCallback<ModbusReadRequestBlueprint> readCallback = callbackCapturer.getValue();
assertNotNull(readCallback);
ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
Exception error = Mockito.mock(Exception.class);
ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
assertNotNull(thingHandler);
ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
ModbusDataThingHandler child2 = Mockito.mock(ModbusDataThingHandler.class);
AsyncModbusFailure<ModbusReadRequestBlueprint> result = new AsyncModbusFailure<ModbusReadRequestBlueprint>(request, error);
// has one data child
thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
readCallback.handle(result);
verify(child1).handleReadError(result);
verifyNoMoreInteractions(child1);
verifyNoMoreInteractions(child2);
reset(child1);
// two children (one child initialized)
thingHandler.childHandlerInitialized(child2, Mockito.mock(Thing.class));
readCallback.handle(result);
verify(child1).handleReadError(result);
verify(child2).handleReadError(result);
verifyNoMoreInteractions(child1);
verifyNoMoreInteractions(child2);
reset(child1);
reset(child2);
// one child disposed
thingHandler.childHandlerDisposed(child1, Mockito.mock(Thing.class));
readCallback.handle(result);
verify(child2).handleReadError(result);
verifyNoMoreInteractions(child1);
verifyNoMoreInteractions(child2);
}
use of org.openhab.core.io.transport.modbus.ModbusFailureCallback in project openhab-core by openhab.
the class ModbusManagerImpl method executeOperation.
/**
* Execute operation using a retry mechanism.
*
* This is a helper function for executing read and write operations and handling the exceptions in a common way.
*
* 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 resetting the connection type.
*
* @param task
* @param oneOffTask
* @param operation
*/
private <R, C extends ModbusResultCallback, F extends ModbusFailureCallback<R>, T extends TaskWithEndpoint<R, C, F>> void executeOperation(T task, boolean oneOffTask, ModbusOperation<T> operation) {
AggregateStopWatch timer = new AggregateStopWatch();
timer.total.resume();
String operationId = timer.operationId;
ModbusSlaveConnectionFactoryImpl connectionFactory = this.connectionFactory;
if (connectionFactory == null) {
// deactivated manager
logger.trace("Deactivated manager - aborting operation.");
return;
}
logTaskQueueInfo();
R request = task.getRequest();
ModbusSlaveEndpoint endpoint = task.getEndpoint();
F failureCallback = task.getFailureCallback();
int maxTries = task.getMaxTries();
AtomicReference<@Nullable Exception> lastError = new AtomicReference<>();
long retryDelay = getEndpointPoolConfiguration(endpoint).getInterTransactionDelayMillis();
if (maxTries <= 0) {
throw new IllegalArgumentException("maxTries should be positive");
}
Optional<ModbusSlaveConnection> connection = Optional.empty();
try {
logger.trace("Starting new operation with task {}. Trying to get connection [operation ID {}]", task, operationId);
connection = getConnection(timer, oneOffTask, task);
logger.trace("Operation with task {}. Got a connection {} [operation ID {}]", task, connection.isPresent() ? "successfully" : "which was unconnected (connection issue)", operationId);
if (!connection.isPresent()) {
// Could not acquire connection, time to abort
// Error logged already, error callback called as well
logger.trace("Initial connection was not successful, aborting. [operation ID {}]", operationId);
return;
}
if (scheduledThreadPoolExecutor == null) {
logger.debug("Manager has been shut down, aborting proecssing request {} [operation ID {}]", request, operationId);
return;
}
int tryIndex = 0;
/**
* last execution is tracked such that the endpoint is not spammed on retry. First retry can be executed
* right away since getConnection ensures enough time has passed since last transaction. More precisely,
* ModbusSlaveConnectionFactoryImpl sleeps on activate() (i.e. before returning connection).
*/
@Nullable Long lastTryMillis = null;
while (tryIndex < maxTries) {
logger.trace("Try {} out of {} [operation ID {}]", tryIndex + 1, maxTries, operationId);
if (!connection.isPresent()) {
// Connection was likely reseted with previous try, and connection was not successfully
// re-established. Error has been logged, time to abort.
logger.trace("Try {} out of {}. Connection was not successful, aborting. [operation ID {}]", tryIndex + 1, maxTries, operationId);
return;
}
if (Thread.interrupted()) {
logger.warn("Thread interrupted. Aborting operation [operation ID {}]", operationId);
return;
}
// Check poll task is still registered (this is all asynchronous)
if (!oneOffTask && task instanceof PollTask) {
verifyTaskIsRegistered((PollTask) task);
}
// Let's ensure that enough time is between the retries
logger.trace("Ensuring that enough time passes before retrying again. Sleeping if necessary [operation ID {}]", operationId);
long slept = ModbusSlaveConnectionFactoryImpl.waitAtleast(lastTryMillis, retryDelay);
logger.trace("Sleep ended, slept {} [operation ID {}]", slept, operationId);
boolean willRetry = false;
try {
tryIndex++;
willRetry = tryIndex < maxTries;
operation.accept(timer, task, connection.get());
lastError.set(null);
break;
} catch (IOException e) {
lastError.set(new ModbusSlaveIOExceptionImpl(e));
// 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 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 resetting the connection. Error details: {} {} [operation ID {}]", tryIndex, request, e.getClass().getName(), e.getMessage(), operationId);
}
if (endpoint instanceof ModbusSerialSlaveEndpoint) {
// Workaround for https://github.com/openhab/openhab-core/issues/1842
// Avoid disconnect/re-connect serial interfaces
logger.debug("Skipping invalidation of serial connection to workaround openhab-core#1842.");
} else {
// Invalidate connection, and empty (so that new connection is acquired before new retry)
timer.connection.timeConsumer(c -> invalidate(endpoint, c), connection);
connection = Optional.empty();
}
continue;
} catch (ModbusIOException e) {
lastError.set(new ModbusSlaveIOExceptionImpl(e));
// 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 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 resetting the connection. Error details: {} {} [operation ID {}]", tryIndex, request, e.getClass().getName(), e.getMessage(), operationId);
}
if (endpoint instanceof ModbusSerialSlaveEndpoint) {
// Workaround for https://github.com/openhab/openhab-core/issues/1842
// Avoid disconnect/re-connect serial interfaces
logger.debug("Skipping invalidation of serial connection to workaround openhab-core#1842.");
} else {
// Invalidate connection, and empty (so that new connection is acquired before new retry)
timer.connection.timeConsumer(c -> invalidate(endpoint, c), connection);
connection = Optional.empty();
}
continue;
} catch (ModbusSlaveException e) {
lastError.set(new ModbusSlaveErrorResponseExceptionImpl(e));
// Slave returned explicit error response, no reason to re-establish new connection
if (willRetry) {
logger.warn("Try {} out of {} failed when executing request ({}). Will try again soon. Error was: {} {} [operation ID {}]", tryIndex, maxTries, request, e.getClass().getName(), e.getMessage(), operationId);
} else {
logger.error("Last try {} failed when executing request ({}). Aborting. Error was: {} {} [operation ID {}]", tryIndex, request, e.getClass().getName(), e.getMessage(), operationId);
}
continue;
} catch (ModbusUnexpectedTransactionIdException | ModbusUnexpectedResponseFunctionCodeException | ModbusUnexpectedResponseSizeException e) {
lastError.set(e);
// 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. 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. Resetting the connection. Error details: {} {} [operation ID {}]", tryIndex, request, e.getClass().getName(), e.getMessage(), operationId);
}
if (endpoint instanceof ModbusSerialSlaveEndpoint) {
// Workaround for https://github.com/openhab/openhab-core/issues/1842
// Avoid disconnect/re-connect serial interfaces
logger.debug("Skipping invalidation of serial connection to workaround openhab-core#1842.");
} else {
// Invalidate connection, and empty (so that new connection is acquired before new retry)
timer.connection.timeConsumer(c -> invalidate(endpoint, c), connection);
connection = Optional.empty();
}
continue;
} catch (ModbusException e) {
lastError.set(e);
// 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 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 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)
timer.connection.timeConsumer(c -> invalidate(endpoint, c), connection);
connection = Optional.empty();
continue;
} finally {
lastTryMillis = System.currentTimeMillis();
// Try to re-establish connection.
if (willRetry && !connection.isPresent()) {
connection = getConnection(timer, oneOffTask, task);
}
}
}
Exception exception = lastError.get();
if (exception != null) {
// All retries failed with some error
timer.callback.timeRunnable(() -> {
invokeCallbackWithError(request, failureCallback, exception);
});
}
} catch (PollTaskUnregistered e) {
logger.warn("Poll task was unregistered -- not executing/proceeding with the poll: {} [operation ID {}]", e.getMessage(), operationId);
return;
} catch (InterruptedException e) {
logger.warn("Poll task was canceled -- not executing/proceeding with the poll: {} [operation ID {}]", e.getMessage(), operationId);
if (endpoint instanceof ModbusSerialSlaveEndpoint) {
// Workaround for https://github.com/openhab/openhab-core/issues/1842
// Avoid disconnect/re-connect serial interfaces
logger.debug("Skipping invalidation of serial connection to workaround openhab-core#1842.");
} else {
// Invalidate connection, and empty (so that new connection is acquired before new retry)
timer.connection.timeConsumer(c -> invalidate(endpoint, c), connection);
connection = Optional.empty();
}
} finally {
timer.connection.timeConsumer(c -> returnConnection(endpoint, c), connection);
logger.trace("Connection was returned to the pool, ending operation [operation ID {}]", operationId);
timer.suspendAllRunning();
logger.debug("Modbus operation ended, timing info: {} [operation ID {}]", timer, operationId);
}
}
Aggregations