/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.nikobus.internal.core;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.openhab.binding.nikobus.internal.NikobusBinding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Monitor which listens to nikobus commands and checks if ACK commands are
* received within a given timeout.
*
* @author Davy Vanherbergen
* @since 1.3.0
*/
public class NikobusAckMonitor implements NikobusCommandListener {
private static Logger log = LoggerFactory.getLogger(NikobusAckMonitor.class);
private NikobusCommand command;
private LinkedBlockingQueue<String> receivedCommands = new LinkedBlockingQueue<String>();
private String name = UUID.randomUUID().toString();
/**
* Create a new monitor for an ACK command.
*
* @param command
* for which to monitor an ACK
*/
public NikobusAckMonitor(NikobusCommand command) {
this.command = command;
}
@Override
public void processNikobusCommand(NikobusCommand command, NikobusBinding binding) {
log.trace("Processing nikobus command {}", command.getCommand());
receivedCommands.add(command.getCommand());
}
/**
* Wait for the reception of an ACK message. If no message was received
* within the timeout specified in the command, an exception is thrown.
*
* When the maxRetryCount of a command is > 1, the command will be resent up
* to maxRetryCount times or until a successful ACK is reached. When a
* command is being resent, the timeout is used for every send, so the
* effective timeout = (maxRetryCount * timeout)
*
* @param commandSender
* sender to use for (re)sending command
*
* @throws Exception
* if no ACK received within timeout..
*/
public void waitForAck(NikobusCommandSender commandSender) throws TimeoutException {
// initial send...
commandSender.sendCommand(command);
// check if the ACK is there
if (isAckReceived()) {
return;
}
// resend...
while (command.getSentCount() < command.getMaxRetryCount()) {
if (command.getSentCount() > 0) {
// only resend if the first command was actually sent..
commandSender.sendCommand(command);
}
// check if the ACK is there
if (isAckReceived()) {
return;
}
}
// no ACK received if we get here...
throw new TimeoutException("No ACK received within timeout and retry count.");
}
/**
* Send a command and wait for an ACK until the timeout has been reached.
*
* @return true if an ACK was received within the timeout limit.
*/
private boolean isAckReceived() {
String ack = null;
long availableTime = command.getTimeout();
long startTime = System.currentTimeMillis();
while (availableTime > 0) {
try {
ack = receivedCommands.poll(availableTime, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.warn("MonitorThread interrupted.");
break;
}
if (ack != null && ack.startsWith(command.getAck().toUpperCase())) {
// good ACK received..
log.trace("Received expected ack '{}'", ack);
return true;
} else if (ack == null) {
log.trace("No ack received within poll time ({}).", command.getTimeout());
}
availableTime = command.getTimeout() - (System.currentTimeMillis() - startTime);
}
return false;
}
@Override
public String getName() {
return name;
}
}