/**
* 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.util.HashMap;
import java.util.Map;
import org.openhab.binding.zwave.internal.protocol.SerialMessage;
import org.openhab.binding.zwave.internal.protocol.SerialMessage.SerialMessagePriority;
import org.openhab.binding.zwave.internal.protocol.ZWaveController;
import org.openhab.binding.zwave.internal.protocol.ZWaveNode;
import org.openhab.binding.zwave.internal.protocol.ZWaveNodeState;
import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveCommandClass;
import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveCommandClass.CommandClass;
import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveWakeUpCommandClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class processes a serial message from the zwave controller
*
* @author Chris Jackson
* @since 1.5.0
*/
public class SendDataMessageClass extends ZWaveCommandProcessor {
private static final Logger logger = LoggerFactory.getLogger(SendDataMessageClass.class);
@Override
public boolean handleResponse(ZWaveController zController, SerialMessage lastSentMessage,
SerialMessage incomingMessage) {
logger.trace("Handle Message Send Data Response");
if (incomingMessage.getMessagePayloadByte(0) != 0x00) {
logger.debug("NODE {}: Sent Data successfully placed on stack.", lastSentMessage.getMessageNode());
} else {
// This is an error. This means that the transaction is complete!
// Set the flag, and return false.
logger.error("NODE {}: Sent Data was not placed on stack due to error {}.",
lastSentMessage.getMessageNode(), incomingMessage.getMessagePayloadByte(0));
// We ought to cancel the transaction
lastSentMessage.setTransactionCanceled();
return false;
}
return true;
}
@Override
public boolean handleRequest(ZWaveController zController, SerialMessage lastSentMessage,
SerialMessage incomingMessage) {
logger.trace("Handle Message Send Data Request");
int callbackId = incomingMessage.getMessagePayloadByte(0);
TransmissionState status = TransmissionState.getTransmissionState(incomingMessage.getMessagePayloadByte(1));
if (status == null) {
logger.warn("Transmission state not found, ignoring.");
return false;
}
ZWaveNode node = zController.getNode(lastSentMessage.getMessageNode());
if (node == null) {
logger.warn("Node not found!");
return false;
}
logger.debug("NODE {}: SendData Request. CallBack ID = {}, Status = {}({})", node.getNodeId(), callbackId,
status.getLabel(), status.getKey());
if (lastSentMessage == null || lastSentMessage.getCallbackId() != callbackId) {
logger.warn("NODE {}: Already processed another send data request for this callback Id, ignoring.",
node.getNodeId());
return false;
}
// This response is our controller ACK
lastSentMessage.setAckRecieved();
switch (status) {
case COMPLETE_OK:
// Consider this as a received frame since the controller did receive an ACK from the device.
node.incrementReceiveCount();
// If the node is DEAD, but we've just received an ACK from it, then it's not dead!
if (node.isDead()) {
node.setNodeState(ZWaveNodeState.ALIVE);
;
} else {
node.resetResendCount();
}
checkTransactionComplete(lastSentMessage, incomingMessage);
return true;
case COMPLETE_NO_ACK:
// Handle WAKE_UP_NO_MORE_INFORMATION differently
// Since the system can time out if the node goes to sleep before
// we get the response, then don't treat this like a timeout
byte[] payload = lastSentMessage.getMessagePayload();
if (payload.length >= 4 && (payload[2] & 0xFF) == ZWaveCommandClass.CommandClass.WAKE_UP.getKey()
&& (payload[3] & 0xFF) == ZWaveWakeUpCommandClass.WAKE_UP_NO_MORE_INFORMATION) {
checkTransactionComplete(lastSentMessage, incomingMessage);
logger.debug("NODE {}: WAKE_UP_NO_MORE_INFORMATION. Treated as ACK.", node.getNodeId());
return true;
}
case COMPLETE_FAIL:
case COMPLETE_NOT_IDLE:
case COMPLETE_NOROUTE:
try {
handleFailedSendDataRequest(zController, lastSentMessage);
} finally {
transactionComplete = true;
}
break;
default:
break;
}
return false;
}
public boolean handleFailedSendDataRequest(ZWaveController zController, SerialMessage originalMessage) {
ZWaveNode node = zController.getNode(originalMessage.getMessageNode());
logger.trace("NODE {}: Handling failed message.", node.getNodeId());
// Increment the resend count.
// This will set the node to DEAD if we've exceeded the retries.
node.incrementResendCount();
// No retries if the node is DEAD or FAILED
if (node.isDead()) {
logger.error("NODE {}: Node is DEAD. Dropping message.", node.getNodeId());
return false;
}
// If this device isn't listening, queue the message in the wakeup class
if (!node.isListening() && !node.isFrequentlyListening()) {
ZWaveWakeUpCommandClass wakeUpCommandClass = (ZWaveWakeUpCommandClass) node
.getCommandClass(CommandClass.WAKE_UP);
if (wakeUpCommandClass != null) {
// It's a battery operated device, place in wake-up queue.
wakeUpCommandClass.setAwake(false);
wakeUpCommandClass.processOutgoingWakeupMessage(originalMessage);
return false;
}
}
logger.error("NODE {}: Got an error while sending data. Resending message.", node.getNodeId());
// Set priority to Immediate since this is a retry
originalMessage.setPriority(SerialMessagePriority.Immediate);
zController.enqueue(originalMessage);
return true;
}
/**
* Transmission state enumeration. Indicates the
* transmission state of the message to the node.
*
* @author Jan-Willem Spuij
* @ since 1.3.0
*/
public enum TransmissionState {
COMPLETE_OK(0x00, "Transmission complete and ACK received"),
COMPLETE_NO_ACK(0x01, "Transmission complete, no ACK received"),
COMPLETE_FAIL(0x02, "Transmission failed"),
COMPLETE_NOT_IDLE(0x03, "Transmission failed, network busy"),
COMPLETE_NOROUTE(0x04, "Tranmission complete, no return route");
/**
* A mapping between the integer code and its corresponding transmission state
* class to facilitate lookup by code.
*/
private static Map<Integer, TransmissionState> codeToTransmissionStateMapping;
private int key;
private String label;
private TransmissionState(int key, String label) {
this.key = key;
this.label = label;
}
private static void initMapping() {
codeToTransmissionStateMapping = new HashMap<Integer, TransmissionState>();
for (TransmissionState s : values()) {
codeToTransmissionStateMapping.put(s.key, s);
}
}
/**
* Lookup function based on the transmission state code.
* Returns null when there is no transmission state with code i.
*
* @param i the code to lookup
* @return enumeration value of the transmission state.
*/
public static TransmissionState getTransmissionState(int i) {
if (codeToTransmissionStateMapping == null) {
initMapping();
}
return codeToTransmissionStateMapping.get(i);
}
/**
* @return the key
*/
public int getKey() {
return key;
}
/**
* @return the label
*/
public String getLabel() {
return label;
}
}
}