/** * 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.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.openhab.binding.zwave.internal.protocol.SerialMessage; import org.openhab.binding.zwave.internal.protocol.SerialMessage.SerialMessageClass; import org.openhab.binding.zwave.internal.protocol.ZWaveController; import org.openhab.binding.zwave.internal.protocol.ZWaveNode; 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.openhab.binding.zwave.internal.protocol.event.ZWaveInclusionEvent; import org.openhab.binding.zwave.internal.protocol.event.ZWaveNodeInfoEvent; import org.openhab.binding.zwave.internal.protocol.initialization.ZWaveNodeInitStage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class processes a Node Information Frame (NIF) message from the zwave controller * * @author Chris Jackson * @since 1.5.0 */ public class ApplicationUpdateMessageClass extends ZWaveCommandProcessor { private static final Logger logger = LoggerFactory.getLogger(ApplicationUpdateMessageClass.class); @Override public boolean handleRequest(ZWaveController zController, SerialMessage lastSentMessage, SerialMessage incomingMessage) { int nodeId; boolean result = true; UpdateState updateState = UpdateState.getUpdateState(incomingMessage.getMessagePayloadByte(0)); switch (updateState) { case NODE_INFO_RECEIVED: // We've received a NIF, and this contains the node ID. nodeId = incomingMessage.getMessagePayloadByte(1); logger.debug("NODE {}: Application update request. Node information received.", nodeId); int length = incomingMessage.getMessagePayloadByte(2); ZWaveNode node = zController.getNode(nodeId); if (node == null) { logger.debug("NODE {}: Application update request. Node not known!", nodeId); // We've received a NIF from a node we don't know. // This could happen if we add a new node using a different controller than OH. // We handle this the same way as if included through an AddNode packet. // This allows everyone to be notified. if (nodeId > 0 && nodeId <= 232) { zController.notifyEventListeners(new ZWaveInclusionEvent(ZWaveInclusionEvent.Type.IncludeDone, incomingMessage.getMessagePayloadByte(2))); } break; } node.resetResendCount(); // Remember that we've received this so we can continue initialisation node.setApplicationUpdateReceived(true); // If we're finished initialisation, then we can treat this like a HAIL if (node.getNodeInitializationStage() == ZWaveNodeInitStage.DONE) { // If this node supports associations, then assume this should be handled through that mechanism if (node.getCommandClass(CommandClass.ASSOCIATION) == null) { // If we receive an Application Update Request and the node is already // fully initialised we assume this is a request to the controller to // re-get the current node values logger.debug("NODE {}: Application update request. Requesting node state.", nodeId); zController.pollNode(node); } } else { List<Integer> nif = new ArrayList<Integer>(); for (int i = 6; i < length + 3; i++) { int data = incomingMessage.getMessagePayloadByte(i); if (data == 0xef) { // TODO: Implement control command classes break; } logger.trace(String.format("NODE %d: Command class 0x%02X is supported.", nodeId, data)); // See if the command class already exists on the node CommandClass commandClass = CommandClass.getCommandClass(data); if (node.getCommandClass(commandClass) == null) { // add it ZWaveCommandClass zwaveCommandClass = ZWaveCommandClass.getInstance(data, node, zController); if (zwaveCommandClass != null) { logger.trace( String.format("NODE %d: Application update request. Adding Command class %s.", nodeId, commandClass)); node.addCommandClass(zwaveCommandClass); } } } node.updateNIF(nif); } // Notify we received an info frame zController.notifyEventListeners(new ZWaveNodeInfoEvent(nodeId)); // Treat the node information frame as a wakeup ZWaveWakeUpCommandClass wakeUp = (ZWaveWakeUpCommandClass) node .getCommandClass(ZWaveCommandClass.CommandClass.WAKE_UP); if (wakeUp != null) { wakeUp.setAwake(true); } break; case NODE_INFO_REQ_FAILED: // Make sure we can correlate the request before we use the nodeId if (lastSentMessage.getMessageClass() != SerialMessageClass.RequestNodeInfo) { logger.warn("Got ApplicationUpdateMessage without request, ignoring. Last message was {}.", lastSentMessage.getMessageClass()); return false; } // The failed message doesn't contain the node number, so use the info from the request. nodeId = lastSentMessage.getMessageNode(); logger.debug("NODE {}: Application update request. Node Info Request Failed.", nodeId); // Handle retries if (--lastSentMessage.attempts >= 0) { logger.error("NODE {}: Got Node Info Request Failed. Requeueing", nodeId); zController.enqueue(lastSentMessage); } else { logger.warn("NODE {}: Node Info Request Failed 3x. Discarding message: {}", nodeId, lastSentMessage.toString()); } // Transaction is not successful incomingMessage.setTransactionCanceled(); result = false; break; default: logger.warn("TODO: Implement Application Update Request Handling of {} ({}).", updateState.getLabel(), updateState.getKey()); } // Check if this completes the transaction checkTransactionComplete(lastSentMessage, incomingMessage); return result; } /** * Update state enumeration. Indicates the type of application update state that was sent. * * @author Jan-Willem Spuij * @ since 1.3.0 */ public enum UpdateState { NODE_INFO_RECEIVED(0x84, "Node info received"), NODE_INFO_REQ_DONE(0x82, "Node info request done"), NODE_INFO_REQ_FAILED(0x81, "Node info request failed"), ROUTING_PENDING(0x80, "Routing pending"), NEW_ID_ASSIGNED(0x40, "New ID Assigned"), DELETE_DONE(0x20, "Delete done"), SUC_ID(0x10, "SUC ID"); /** * A mapping between the integer code and its corresponding update state * class to facilitate lookup by code. */ private static Map<Integer, UpdateState> codeToUpdateStateMapping; private int key; private String label; private UpdateState(int key, String label) { this.key = key; this.label = label; } private static void initMapping() { codeToUpdateStateMapping = new HashMap<Integer, UpdateState>(); for (UpdateState s : values()) { codeToUpdateStateMapping.put(s.key, s); } } /** * Lookup function based on the update state code. * Returns null when there is no update state with code i. * * @param i the code to lookup * @return enumeration value of the update state. */ public static UpdateState getUpdateState(int i) { if (codeToUpdateStateMapping == null) { initMapping(); } return codeToUpdateStateMapping.get(i); } /** * @return the key */ public int getKey() { return key; } /** * @return the label */ public String getLabel() { return label; } } }