/**
* 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.zwave.internal.protocol.serialmessage;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.openhab.binding.zwave.internal.protocol.SerialMessage;
import org.openhab.binding.zwave.internal.protocol.ZWaveController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class processes a serial message from the zwave controller
* This class is the base class for the serial message class. It handles
* the request from the application, and the processing of the responses
* from the controller.
* When a message is sent to the controller, the controller responds with a RESPONSE.
* When the controller has further data, it responds with a REQUEST.
* These calls map to the handleResponse and handleRequest methods
* which must be overridden by the individual classes.
*
* @author Chris Jackson
* @since 1.5.0
*/
public abstract class ZWaveCommandProcessor {
private static final Logger logger = LoggerFactory.getLogger(ZWaveCommandProcessor.class);
private static HashMap<SerialMessage.SerialMessageClass, Class<? extends ZWaveCommandProcessor>> messageMap = null;
protected boolean transactionComplete = false;
/**
* With the addition of secure message support, the reply to a message will often occur after a different reply.
* To account for this condition, we store replies in a table
*
* Map of Long (received time) and {@link SerialMessage}. Use TreeMap so it's sorted from oldest to newest
*/
private final Map<Long, SerialMessage> incomingMessageTable = new TreeMap<Long, SerialMessage>();
public ZWaveCommandProcessor() {
}
/**
* Checks if the processor marked the transaction as complete
*
* @return true is the transaction was completed.
*/
public boolean isTransactionComplete() {
return transactionComplete;
}
/**
* Perform a check to see if this is the expected reply
* and we can complete the transaction
*
* @param lastSentMessage The original message we sent to the controller
* @param incomingMessage The response from the controller
*/
protected void checkTransactionComplete(SerialMessage lastSentMessage, SerialMessage latestIncomingMessage) {
// Put the message in our table so it will be processed now or later
incomingMessageTable.put(System.currentTimeMillis(), latestIncomingMessage);
// check if we're waiting for an ACK from the controller
// This is used for multi-stage transactions to ensure we get all parts of the
// transaction before completing.
if (lastSentMessage.isAckPending()) {
logger.trace("Checking transaction complete: Message has Ack Pending: {}", lastSentMessage);
// Return until we get the ack, then come back and compare. This is necessary since, per ZWaveSendThread, we
// sometimes get the response before the ack. See ZWaveSendThreadcomment starting with "A transaction
// consists of (up to) 4 parts"
return;
}
logger.debug("Sent message {}", lastSentMessage.toString());
logger.debug("Recv message {}", latestIncomingMessage.toString());
logger.debug("Checking transaction complete: Sent message {}", lastSentMessage.toString());
final Iterator<Map.Entry<Long, SerialMessage>> iter = incomingMessageTable.entrySet().iterator();
final long expired = System.currentTimeMillis() - 10000; // Discard responses from 10 seconds ago or longer
while (iter.hasNext()) {
final Map.Entry<Long, SerialMessage> entry = iter.next();
// Check if it's expired and remove it if it is
if (entry.getKey() < expired) {
iter.remove();
continue;
}
final SerialMessage anIncomingMessage = entry.getValue();
logger.debug("Checking transaction complete: Recv message {}", anIncomingMessage.toString());
if (anIncomingMessage.getMessageClass() == lastSentMessage.getExpectedReply()
&& !anIncomingMessage.isTransactionCanceled()) {
logger.debug(
"Checking transaction complete: class={}, callback id={}, expected={}, cancelled={} transaction complete!",
anIncomingMessage.getMessageClass(), lastSentMessage.getCallbackId(),
lastSentMessage.getExpectedReply(), anIncomingMessage.isTransactionCanceled());
transactionComplete = true;
iter.remove(); // We've processed this reply
return;
} else {
logger.debug(
"Checking transaction complete: class={}, callback id={}, expected={}, cancelled={} mismatch",
anIncomingMessage.getMessageClass(), lastSentMessage.getCallbackId(),
lastSentMessage.getExpectedReply(), anIncomingMessage.isTransactionCanceled());
}
}
}
/**
* Method for handling the response from the controller
*
* @param zController the ZWave controller
* @param lastSentMessage The original message we sent to the controller
* @param incomingMessage The response from the controller
* @return
*/
public boolean handleResponse(ZWaveController zController, SerialMessage lastSentMessage,
SerialMessage incomingMessage) {
logger.warn("TODO: {} unsupported RESPONSE.", incomingMessage.getMessageClass().getLabel());
return false;
}
/**
* Method for handling the request from the controller
*
* @param zController the ZWave controller
* @param lastSentMessage The original message we sent to the controller
* @param incomingMessage The response from the controller
* @return
*/
public boolean handleRequest(ZWaveController zController, SerialMessage lastSentMessage,
SerialMessage incomingMessage) {
logger.warn("TODO: {} unsupported REQUEST.", incomingMessage.getMessageClass().getLabel());
return false;
}
/**
* Returns the message processor for the specified message class
*
* @param serialMessage The message class required to be processed
* @return The message processor
*/
public static ZWaveCommandProcessor getMessageDispatcher(SerialMessage.SerialMessageClass serialMessage) {
if (messageMap == null) {
messageMap = new HashMap<SerialMessage.SerialMessageClass, Class<? extends ZWaveCommandProcessor>>();
messageMap.put(SerialMessage.SerialMessageClass.AddNodeToNetwork, AddNodeMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.ApplicationCommandHandler,
ApplicationCommandMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.ApplicationUpdate, ApplicationUpdateMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.AssignReturnRoute, AssignReturnRouteMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.AssignSucReturnRoute,
AssignSucReturnRouteMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.DeleteReturnRoute, DeleteReturnRouteMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.EnableSuc, EnableSucMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.GetRoutingInfo, GetRoutingInfoMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.GetVersion, GetVersionMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.GetSucNodeId, GetSucNodeIdMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.GetControllerCapabilities,
GetControllerCapabilitiesMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.IdentifyNode, IdentifyNodeMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.MemoryGetId, MemoryGetIdMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.RemoveFailedNodeID, RemoveFailedNodeMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.IsFailedNodeID, IsFailedNodeMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.RemoveNodeFromNetwork, RemoveNodeMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.RequestNodeInfo, RequestNodeInfoMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.RequestNodeNeighborUpdate,
RequestNodeNeighborUpdateMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.SendData, SendDataMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.SerialApiGetCapabilities,
SerialApiGetCapabilitiesMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.SerialApiGetInitData,
SerialApiGetInitDataMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.SerialApiSetTimeouts,
SerialApiSetTimeoutsMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.SerialApiSoftReset, SerialApiSoftResetMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.SetSucNodeID, SetSucNodeMessageClass.class);
messageMap.put(SerialMessage.SerialMessageClass.SetDefault, ControllerSetDefaultMessageClass.class);
}
Constructor<? extends ZWaveCommandProcessor> constructor;
try {
if (messageMap.get(serialMessage) == null) {
logger.warn("SerialMessage class {} is not implemented!", serialMessage.getLabel());
return null;
}
constructor = messageMap.get(serialMessage).getConstructor();
return constructor.newInstance();
} catch (Exception e) {
logger.error("Command processor error");
}
return null;
}
}