public void init() {
   queue.sendMsg(Msg.createZWaveGetVersion((byte) 0xFF), QueuePriority.Command);
   queue.sendMsg(Msg.createZWaveMemoryGetId((byte) 0xFF), QueuePriority.Command);
   queue.sendMsg(Msg.createZWaveGetControllerCapabilities((byte) 0xFF), QueuePriority.Command);
   queue.sendMsg(Msg.createZWaveSerialAPIGetCapabilities((byte) 0xFF), QueuePriority.Command);
   queue.sendMsg(Msg.createZWaveGetSUCNodeId((byte) 0xFF), QueuePriority.Command);
 }
  private boolean handleErrorResponse(byte error, byte nodeId, String funcStr, boolean sleepcheck) {
    Node node = getNode(nodeId);
    if (error == Defs.TRANSMIT_COMPLETE_NOROUTE) {
      logs.add(String.format("Error: %s failed. No route found.", funcStr));
      // if (node != null) {
      // node.setNodeAlive(false);
      // }
    } else if (error == Defs.TRANSMIT_COMPLETE_NO_ACK) {
      logs.add(String.format("Error: %s failed. No ACK found.", funcStr));
      if (queue.getCurrentMsg() != null) {
        if (moveMsgToWakeUpQueue(SafeCast.nodeIdFromMsg(queue.getCurrentMsg()), sleepcheck)) {
          return true;
        }
      }
    } else if (error == Defs.TRANSMIT_COMPLETE_FAIL) {
      logs.add(String.format("Error: %s failed. Network busy.", funcStr));
    } else if (error == Defs.TRANSMIT_COMPLETE_NOT_IDLE) {
      logs.add(String.format("Error: %s failed. Network busy.", funcStr));
    }

    if (node != null && node.incErrorCount() >= 3) {
      node.setNodeAlive(false);
    }
    return false;
  }
  public void handleApplicationCommandHandlerRequest(byte[] data) {
    byte status = data[2];
    byte nodeId = data[3];
    byte classId = data[5];

    Node node = getNode(nodeId);
    if ((status & Defs.RECEIVE_STATUS_ROUTED_BUSY) != 0) {}
    if ((status & Defs.RECEIVE_STATUS_TYPE_BROAD) != 0) {
      /* receive 500ms */
    }

    if (node != null) {
      if (queue.getExpectedReply() == Defs.FUNC_ID_APPLICATION_COMMAND_HANDLER
          && queue.getExpectedNodeId() == nodeId) {
        // update RTT
      } else {
        // received unsolicited
      }
    }

    if ((byte) 0x22 == classId) {
      // TODO: Test this class function or implement
    } else if ((byte) 0x21 == classId) {
      // TODO: Test this class function or implement
    } else if (node != null) {
      node.applicationCommandHandler(data);
    }
  }
  public boolean handleApplicationUpdateRequest(byte[] data) {
    boolean messageRemoved = false;
    byte nodeId = data[3];
    Node node = getNode(nodeId);
    Node tnode = null;

    if (node != null && !node.isNodeAlive()) {
      node.setNodeAlive(true);
    }
    switch (data[2]) {
      case Defs.UPDATE_STATE_SUC_ID:
        logs.add(String.format("Update SUC Id node %d", SafeCast.toInt(nodeId)));
        sucNodeId = nodeId;
        break;
      case Defs.UPDATE_STATE_DELETE_DONE:
        logs.add(String.format("Remove node %d", SafeCast.toInt(nodeId)));
        removeNode(nodeId);
        break;
      case Defs.UPDATE_STATE_NEW_ID_ASSIGNED:
        logs.add(String.format("Add node %d", SafeCast.toInt(nodeId)));
        addNode(nodeId);
        break;
      case Defs.UPDATE_STATE_ROUTING_PENDING:
        logs.add(String.format("Routing pending node %d", nodeId));
        break;
      case Defs.UPDATE_STATE_NODE_INFO_REQ_FAILED:
        if (queue.getCurrentMsg() != null) {
          logs.add(
              String.format(
                  "Update failed node %d", SafeCast.nodeIdFromMsg(queue.getCurrentMsg())));
          tnode = getNode(SafeCast.nodeIdFromMsg(queue.getCurrentMsg()));
          if (tnode != null) {
            tnode.queryStageRetry(QueryStage.NodeInfo, (byte) 2);
            if (moveMsgToWakeUpQueue(tnode.getNodeId(), true)) {
              messageRemoved = true;
            }
          }
        }
        break;
      case Defs.UPDATE_STATE_NODE_INFO_REQ_DONE:
        logs.add(String.format("Update done node %d", nodeId));
        break;
      case Defs.UPDATE_STATE_NODE_INFO_RECEIVED:
        logs.add(String.format("Update info receive node %d", nodeId));
        if (node != null) {
          node.updateNodeInfo(
              Arrays.copyOfRange(data, 8, data.length), (byte) ((SafeCast.toInt(data[4]) - 3)));
        }
        break;
    }

    if (messageRemoved) {
      queue.removeExpectedAndACK();
    }
    return messageRemoved;
  }
  public void handleGetSerialAPICapabilitiesResponse(byte[] data) {
    primaryController.setSerialAPIVersion(new byte[] {data[2], data[3]});
    primaryController.setManufacturerShortId(
        (short) (SafeCast.toShort(data[4]) << 8 | SafeCast.toShort(data[5])));
    primaryController.setProductShortType(
        (short) (SafeCast.toShort(data[6]) << 8 | SafeCast.toShort(data[7])));
    primaryController.setProductShortId(
        (short) (SafeCast.toShort(data[8]) << 8 | SafeCast.toShort(data[9])));
    primaryController.setApiMask(Arrays.copyOfRange(data, 10, 32));

    logs.add(
        String.format(
            "FUNC_ID_SERIAL_API_GET_CAPABILITIES -- Serial API: v%d.%d,"
                + " Man. Id = 0x%04x, Prod. Type = 0x%04x, Prod. Id = 0x%04x",
            primaryController.getSerialAPIVersion()[0],
            primaryController.getSerialAPIVersion()[1],
            primaryController.getManufacturerShortId(),
            primaryController.getProductShortType(),
            primaryController.getProductShortId()));

    Msg msg;
    if (primaryController.isBridgeController()) {
      msg = new Msg((byte) 0xFF, Defs.REQUEST, Defs.FUNC_ID_ZW_GET_VIRTUAL_NODES, false);
      queue.sendMsg(msg, QueuePriority.Command);
    } else if (primaryController.isAPICallSupported(Defs.FUNC_ID_ZW_GET_RANDOM)) {
      msg = new Msg((byte) 0xFF, Defs.REQUEST, Defs.FUNC_ID_ZW_GET_RANDOM, false);
      msg.append((byte) 32);
      queue.sendMsg(msg, QueuePriority.Command);
    }

    msg = new Msg((byte) 0xFF, Defs.REQUEST, Defs.FUNC_ID_SERIAL_API_GET_INIT_DATA, false);
    queue.sendMsg(msg, QueuePriority.Command);

    msg =
        new Msg(
            (byte) 0xFF, Defs.REQUEST, Defs.FUNC_ID_SERIAL_API_APPL_NODE_INFORMATION, false, false);
    msg.appends(new byte[] {Defs.APPLICATION_NODEINFO_LISTENING, 0x02, 0x01, 0x01, 0x2B});
    // msg.appends(new byte[] { Defs.APPLICATION_NODEINFO_LISTENING, 0x02,
    //		0x01, 0x00 });
    queue.sendMsg(msg, QueuePriority.Command);
  }
  public void handleGetRoutingInfoResponse(byte[] data) {
    Node node = getNode(SafeCast.nodeIdFromMsg(queue.getCurrentMsg()));
    if (node != null) {
      node.setNeighbors(Arrays.copyOfRange(data, 2, 29));
      logs.add(
          String.format(
              "FUNC_ID_ZW_GET_ROUTING_INFO -- Neighbors of node %d are:", node.getNodeId()));
      boolean neighbors = false;
      for (int i = 0; i < 29; i++) {
        for (int j = 0; j < 8; j++) {
          if ((data[2 + i] & (0x01 << j)) != 0) {
            logs.add(String.format("--- Node %d", (i << 3) + j + 1));
            neighbors = true;
          }
        }
      }

      if (!neighbors) {
        logs.add(String.format("no neighbor on node %d", node.getNodeId()));
      }
    }
  }
 // -----------------------------------------------------------------------------------------
 // Request Methods
 // -----------------------------------------------------------------------------------------
 public void handleSendDataRequest(byte[] data, boolean replication) {
   byte nodeId = SafeCast.nodeIdFromMsg(queue.getCurrentMsg());
   if (data[2] != queue.getExpectedCallbackId()) {
     logs.add(
         String.format(
             "Unexpected callback id: received %d != %d",
             SafeCast.toInt(data[2]), SafeCast.toInt(queue.getExpectedCallbackId())));
   } else {
     Node node = getNode(nodeId);
     if (node != null && queue.getCurrentMsg() != null) {
       if (data[3] != 0) {
         if (!handleErrorResponse(
             data[3],
             nodeId,
             replication ? "ZW_REPLICATION_END_DATA" : "ZW_SEND_DATA",
             !replication)) {
           if (queue.getCurrentMsg().isNoOperation()
               && (node.getQueryStage() == QueryStage.Probe1
                   || node.getQueryStage() == QueryStage.Probe2)) {
             node.queryStageRetry(node.getQueryStage(), (byte) 3);
           }
         }
       } else {
         if (queue.getCurrentMsg().isWakeUpNoMoreInformationCommand()) {
           WakeUp wu =
               (WakeUp) node.getCommandClassManager().getCommandClass(WakeUp.COMMAND_CLASS_ID);
           if (wu != null) {
             wu.setAwake(false);
           }
         }
         if (!node.isNodeAlive()) {
           node.setNodeAlive(true);
         }
       }
     }
     queue.setExpectedCallbackId((byte) 0);
   }
 }
  private void commonAddNodeStatusRequestHandler(byte funcId, byte[] data) {
    ControllerCmd cci = queue.getCurrentControllerCmd();
    ControllerState state = ControllerState.Normal;

    if (cci != null) {
      state = cci.getControllerState();
    }
    switch (data[3]) {
      case Defs.ADD_NODE_STATUS_LEARN_READY:
        logs.add("ADD_NODE_STATUS_LEARN_READY");
        if (cci != null) {
          cci.setControllerAdded(false);
        }
        state = ControllerState.Waiting;
        if (cci != null && cci.getControllerCallback() != null) {
          cci.getControllerCallback().onAction(state, null, null);
        }
        break;
      case Defs.ADD_NODE_STATUS_NODE_FOUND:
        logs.add("ADD_NODE_STATUS_NODE_FOUND");
        state = ControllerState.InProgress;
        if (cci != null && cci.getControllerCallback() != null) {
          cci.getControllerCallback().onAction(state, null, null);
        }
        break;
      case Defs.ADD_NODE_STATUS_ADDING_SLAVE:
        logs.add("ADD_NODE_STATUS_ADDING_SLAVE --- Node: " + String.valueOf(data[4]));
        if (cci != null) {
          cci.setControllerAdded(false);
          cci.setControllerCommandNode(data[4]);
        }
        break;
      case Defs.ADD_NODE_STATUS_ADDING_CONTROLLER:
        logs.add("ADD_NODE_STATUS_ADDING_CONTROLLER --- Node: " + String.valueOf(data[4]));
        if (cci != null) {
          cci.setControllerAdded(true);
          cci.setControllerCommandNode(data[4]);
        }
        break;
      case Defs.ADD_NODE_STATUS_PROTOCOL_DONE:
        logs.add("ADD_NODE_STATUS_PROTOCOL_DONE");
        primaryController.addNodeStop(funcId);
        break;
      case Defs.ADD_NODE_STATUS_DONE:
        logs.add("ADD_NODE_STATUS_DONE");
        state = ControllerState.Completed;
        if (cci != null && cci.getControllerCallback() != null) {
          cci.getControllerCallback().onAction(state, null, null);
        }
        if (cci != null && cci.getControllerCommandNode() != (byte) 0xFF) {
          addNode(cci.getControllerCommandNode());
        }
        if (funcId != Defs.FUNC_ID_ZW_ADD_NODE_TO_NETWORK
            && cci != null
            && cci.isControllerAdded()) {
          initAllNodes();
        }

        break;
      case Defs.ADD_NODE_STATUS_FAILED:
        logs.add("ADD_NODE_STATUS_FAILED");
        state = ControllerState.Failed;
        if (cci != null && cci.getControllerCallback() != null) {
          cci.getControllerCallback().onAction(state, null, null);
        }
        queue.removeCurrentMsg();
        primaryController.addNodeStop(funcId);
        break;
      default:
        logs.add("No detected ...");
        break;
    }

    primaryController.updateControllerState(state);
  }
  public void handleRemoveNodeFromNetworkRequest(byte[] data) {
    ControllerCmd cci = queue.getCurrentControllerCmd();

    if (cci == null) {
      return;
    }

    ControllerState state = cci.getControllerState();
    switch (data[3]) {
      case Defs.REMOVE_NODE_STATUS_LEARN_READY:
        logs.add("REMOVE_NODE_STATUS_LEARN_READY");
        state = ControllerState.Waiting;
        cci.setControllerCommandNode((byte) 0);
        if (cci != null && cci.getControllerCallback() != null) {
          cci.getControllerCallback().onAction(state, null, null);
        }
        break;
      case Defs.REMOVE_NODE_STATUS_NODE_FOUND:
        logs.add("REMOVE_NODE_STATUS_NODE_FOUND");
        state = ControllerState.InProgress;
        if (cci != null && cci.getControllerCallback() != null) {
          cci.getControllerCallback().onAction(state, null, null);
        }
        break;
      case Defs.REMOVE_NODE_STATUS_REMOVING_SLAVE:
        logs.add("REMOVE_NODE_STATUS_REMOVING_SLAVE --- Node: " + String.valueOf(data[4]));
        cci.setControllerCommandNode(data[4]);
        break;
      case Defs.REMOVE_NODE_STATUS_REMOVING_CONTROLLER:
        logs.add("REMOVE_NODE_STATUS_REMOVING_CONTROLLER --- Node: " + String.valueOf(data[4]));
        // mCurrentControllerCommand.mControllerCommandNode = data[4];
        cci.setControllerCommandNode(data[4]);
        if (data[4] == (byte) 0) {
          if (data[5] >= 3) {
            for (int i = 0; i < Defs.MAX_TOTAL_NODES; i++) {
              Node node = getNode((byte) i);
              synchronized (node) {
                if (node == null || primaryController == null) {
                  continue;
                }
                if (node.getNodeId() == primaryController.getNodeId()) {
                  continue;
                } // Ignore primary controller
                // See if we can match another way
                if (node.getBasicDeviceClassID() == data[6]
                    && node.getGenericDeviceClassID() == data[7]
                    && node.getSpecificDeviceClassID() == data[8]) {
                  if (cci.getControllerCommandNode() != 0) {
                    // TODO: Alternative controller found
                  } else {
                    cci.setControllerCommandNode(node.getNodeId());
                  }
                }
              }
            }
          } else {
            // TODO: error message not enough data
          }
        } else {
          cci.setControllerCommandNode(data[4]);
        }
        break;
      case Defs.REMOVE_NODE_STATUS_DONE:
        logs.add("REMOVE_NODE_STATUS_DONE");
        state = ControllerState.Completed;
        if (cci != null && cci.getControllerCallback() != null) {
          cci.getControllerCallback().onAction(state, null, null);
        }
        if (!cci.isControllerCommandDone()) {
          primaryController.updateControllerState(ControllerState.Completed);
          primaryController.addNodeStop(Defs.FUNC_ID_ZW_REMOVE_NODE_FROM_NETWORK);
          if (cci.getControllerCommandNode() == (byte) 0) {
            if (data[4] != (byte) 0) {
              cci.setControllerCommandNode(data[4]);
            }
          }

          if (cci.getControllerCommandNode() != (byte) 0
              && cci.getControllerCommandNode() != (byte) 0xFF) {
            removeNode(cci.getControllerCommandNode());
          }
        }
        return;
      case Defs.REMOVE_NODE_STATUS_FAILED:
        logs.add("REMOVE_NODE_STATUS_FAILED");
        primaryController.addNodeStop(Defs.FUNC_ID_ZW_REMOVE_NODE_FROM_NETWORK);
        state = ControllerState.Failed;
        if (cci != null && cci.getControllerCallback() != null) {
          cci.getControllerCallback().onAction(state, null, null);
        }
        break;
      default:
        logs.add("No Detected ...");
        break;
    }

    primaryController.updateControllerState(state);
  }
  public boolean moveMsgToWakeUpQueue(byte targetNodeId, boolean move) {
    Node node = getNode(targetNodeId);
    if (node != null
        && !node.isListeningDevice()
        && !node.isFrequentListeningDevice()
        && primaryController != null
        && targetNodeId != primaryController.getNodeId()) {
      WakeUp wu = (WakeUp) node.getCommandClassManager().getCommandClass(WakeUp.COMMAND_CLASS_ID);
      if (wu != null) {
        wu.setAwake(false);
        if (move) {
          if (queue.getCurrentControllerCmd() != null) {
            queue.removeCurrentMsg();
          }
          if (queue.getCurrentMsg() != null) {
            Msg msg = queue.getCurrentMsg();
            if (targetNodeId == SafeCast.nodeIdFromMsg(msg)) {
              if (!msg.isWakeUpNoMoreInformationCommand() && !msg.isNoOperation()) {
                QueueItem item = new QueueItem();
                item.setCommand(QueueCommand.SendMessage);
                item.setMsg(msg);
                wu.queueItem(item);
              }
              queue.removeCurrentMsg();
            }
          }

          for (int i = 0; i < queue.size(); ++i) {
            Iterator<QueueItem> iter = queue.getQueue().iterator();
            while (iter.hasNext()) {
              boolean remove = false;
              QueueItem it = iter.next();
              if (it.getCommand() == QueueCommand.SendMessage
                  && targetNodeId == SafeCast.nodeIdFromMsg(it.getMsg())) {
                if (!it.getMsg().isWakeUpNoMoreInformationCommand()
                    && !it.getMsg().isNoOperation()) {
                  wu.queueItem(it);
                } else {
                  it.setMsg(null);
                }
                remove = true;
              } else if (it.getCommand() == QueueCommand.QueryStageComplete
                  && targetNodeId == SafeCast.nodeIdFromMsg(it.getMsg())) {
                wu.queueItem(it);
                remove = true;
              } else if (it.getCommand() == QueueCommand.Controller
                  && targetNodeId == SafeCast.nodeIdFromMsg(it.getMsg())) {
                wu.queueItem(it);
                remove = true;
              }

              if (remove) {
                iter.remove();
              }
            }
          }

          if (queue.getCurrentControllerCmd() != null) {
            primaryController.updateControllerState(ControllerState.Sleeping);
            queue.sendControllerCommand(queue.getCurrentControllerCmd());
          }
          return true;
        }
      }
    }

    return false;
  }