/**
   * Handles Multi Instance Encapsulation message. Decapsulates an Application Command message and
   * handles it using the right instance.
   *
   * @param serialMessage the serial message to process.
   * @param offset the offset at which to start procesing.
   * @throws ZWaveSerialMessageException
   */
  private void handleMultiInstanceEncapResponse(SerialMessage serialMessage, int offset)
      throws ZWaveSerialMessageException {
    logger.trace("Process Multi-instance Encapsulation");
    int instance = serialMessage.getMessagePayloadByte(offset);
    int commandClassCode = serialMessage.getMessagePayloadByte(offset + 1);
    CommandClass commandClass = CommandClass.getCommandClass(commandClassCode);

    if (commandClass == null) {
      logger.error(
          String.format(
              "NODE %d: Unsupported command class 0x%02x",
              this.getNode().getNodeId(), commandClassCode));
      return;
    }

    logger.debug(
        String.format(
            "NODE %d: Requested Command Class = %s (0x%02x)",
            this.getNode().getNodeId(), commandClass.getLabel(), commandClassCode));

    ZWaveCommandClass zwaveCommandClass = null;

    // first get command class from endpoint, if supported
    if (this.getVersion() >= 2) {
      ZWaveEndpoint endpoint = this.endpoints.get(instance);
      if (endpoint != null) {
        zwaveCommandClass = endpoint.getCommandClass(commandClass);
        if (zwaveCommandClass == null) {
          logger.warn(
              String.format(
                  "NODE %d: CommandClass %s (0x%02x) not implemented by endpoint %d, fallback to main node.",
                  this.getNode().getNodeId(), commandClass.getLabel(), commandClassCode, instance));
        }
      }
    }

    if (zwaveCommandClass == null) {
      zwaveCommandClass = this.getNode().getCommandClass(commandClass);
    }

    if (zwaveCommandClass == null) {
      logger.error(
          String.format(
              "NODE %d: Unsupported command class %s (0x%02x)",
              this.getNode().getNodeId(), commandClass.getLabel(), commandClassCode));
      return;
    }

    logger.debug(
        "NODE {}: Instance = {}, calling handleApplicationCommandRequest.",
        this.getNode().getNodeId(),
        instance);
    zwaveCommandClass.handleApplicationCommandRequest(serialMessage, offset + 2, instance);
  }
  /**
   * Handles Multi Instance Report message. Handles Report on the number of instances for the
   * command class. This is for Version 1 of the command class.
   *
   * @param serialMessage the serial message to process.
   * @param offset the offset at which to start processing.
   * @throws ZWaveSerialMessageException
   */
  private void handleMultiInstanceReportResponse(SerialMessage serialMessage, int offset)
      throws ZWaveSerialMessageException {
    logger.trace("Process Multi-instance Report");
    int commandClassCode = serialMessage.getMessagePayloadByte(offset);
    int instances = serialMessage.getMessagePayloadByte(offset + 1);

    CommandClass commandClass = CommandClass.getCommandClass(commandClassCode);
    if (commandClass == null) {
      logger.error(
          String.format(
              "NODE %d: Unsupported command class 0x%02x",
              this.getNode().getNodeId(), commandClassCode));
      return;
    }

    logger.debug(
        "NODE {}: Requested Command Class = {}",
        this.getNode().getNodeId(),
        commandClass.getLabel());

    ZWaveCommandClass zwaveCommandClass = this.getNode().getCommandClass(commandClass);
    if (zwaveCommandClass == null) {
      logger.error(
          String.format(
              "NODE %d: Unsupported command class %s (0x%02x)",
              this.getNode().getNodeId(), commandClass.getLabel(), commandClassCode));
      return;
    }

    if (instances == 0) {
      logger.debug("NODE {}: Instances = 0. Setting to 1.", this.getNode().getNodeId());
      instances = 1;
    }

    zwaveCommandClass.setInstances(instances);
    logger.debug(
        "NODE {}: Command class {}, has {} instance(s).",
        this.getNode().getNodeId(),
        commandClass.getLabel(),
        instances);
  }
 /**
  * Gets a SerialMessage with the MULTI_INSTANCE_GET command. Returns the number of instances for
  * this command class.
  *
  * @param the command class to return the number of instances for.
  * @return the serial message.
  */
 public SerialMessage getMultiInstanceGetMessage(CommandClass commandClass) {
   logger.debug(
       "NODE {}: Creating new message for command MULTI_INSTANCE_GET command class {}",
       this.getNode().getNodeId(),
       commandClass.getLabel());
   SerialMessage result =
       new SerialMessage(
           this.getNode().getNodeId(),
           SerialMessageClass.SendData,
           SerialMessageType.Request,
           SerialMessageClass.ApplicationCommandHandler,
           SerialMessagePriority.Get);
   byte[] newPayload = {
     (byte) this.getNode().getNodeId(),
     3,
     (byte) getCommandClass().getKey(),
     (byte) MULTI_INSTANCE_GET,
     (byte) commandClass.getKey()
   };
   result.setMessagePayload(newPayload);
   return result;
 }
  private static void parseConfiguration(String stringUrl, Product product) {
    try {
      URL url = new URL(stringUrl);
      URLConnection connection = url.openConnection();
      int fileLength = connection.getContentLength();

      if (fileLength == -1) {
        System.out.println("Invalide URL or file.");
        return;
      }

      CommandClass currentCC = null;
      Parameter currentParameter = null;
      Association currentAssociation = null;

      XMLInputFactory inputFactory = XMLInputFactory.newInstance();
      InputStream input = connection.getInputStream();
      final XMLEventReader eventReader = inputFactory.createXMLEventReader(input);

      while (eventReader.hasNext()) {
        XMLEvent event = eventReader.nextEvent();
        if (event.isStartElement()) {
          StartElement startElt = event.asStartElement();
          if (startElt.getName().getLocalPart().equals("CommandClass")) {
            CommandClass cc = product.view().createCommandClass();

            Iterator<Attribute> attributes = startElt.getAttributes();
            while (attributes.hasNext()) {
              Attribute next = attributes.next();
              String attrName = next.getName().toString();

              if (attrName.equals("id")) {
                cc.setId(Integer.parseInt(next.getValue(), 16));
              }
            }
            product.addCommandClasses(cc);
            currentCC = cc;

          } else if (startElt.getName().getLocalPart().equals("Value")) {

            Parameter param = product.view().createParameter();

            Iterator<Attribute> attributes = startElt.getAttributes();
            while (attributes.hasNext()) {
              Attribute next = attributes.next();
              String attrName = next.getName().toString();
              if (!next.getValue().equals("")) {
                if (attrName.equals("type")) {
                  param.setType(ParameterType.valueOf(next.getValue().toUpperCase()));
                } else if (attrName.equals("genre")) {
                  param.setGenre(next.getValue());
                } else if (attrName.equals("instance")) {
                  param.setInstance(Integer.valueOf(next.getValue()));
                } else if (attrName.equals("index")) {
                  param.setIndex(Integer.valueOf(next.getValue()));
                } else if (attrName.equals("label")) {
                  param.setLabel(next.getValue());
                } else if (attrName.equals("value")) {
                  param.setValue(next.getValue());
                } else if (attrName.equals("min")) {

                  param.setMin(Integer.valueOf(next.getValue()));
                } else if (attrName.equals("max")) {

                  param.setMax(Integer.valueOf(next.getValue()));
                } else if (attrName.equals("size")) {
                  param.setSize(Integer.valueOf(next.getValue()));
                }
              }
            }
            currentCC.addParameters(param);
            currentParameter = param;

          } else if (startElt.getName().getLocalPart().equals("Help")) {
            // help

          } else if (startElt.getName().getLocalPart().equals("Item")) {
            ParameterItem item = product.view().createParameterItem();

            Iterator<Attribute> attributes = startElt.getAttributes();
            while (attributes.hasNext()) {
              Attribute next = attributes.next();
              String attrName = next.getName().toString();
              if (attrName.equals("label")) {
                item.setLabel(next.getValue());
              } else if (attrName.equals("value")) {
                item.setValue(Integer.valueOf(next.getValue()));
              }
            }
            currentParameter.addItems(item);
          } else if (startElt.getName().getLocalPart().equals("Associations")) {
            Association assoc = product.view().createAssociation();

            Iterator<Attribute> attributes = startElt.getAttributes();
            while (attributes.hasNext()) {
              Attribute next = attributes.next();
              String attrName = next.getName().toString();
              if (attrName.equals("num_groups")) {
                assoc.setNumGroups(Integer.valueOf(next.getValue()));
              }
            }
            currentCC.addAssociations(assoc);
            currentAssociation = assoc;
          } else if (startElt.getName().getLocalPart().equals("Group")) {
            AssociationGroup group = product.view().createAssociationGroup();

            Iterator<Attribute> attributes = startElt.getAttributes();
            while (attributes.hasNext()) {
              Attribute next = attributes.next();
              String attrName = next.getName().toString();
              if (attrName.equals("label")) {
                group.setLabel(next.getValue());
              } else if (attrName.equals("index")) {
                group.setIndex(Integer.valueOf(next.getValue()));
              } else if (attrName.equals("max_associations")) {
                group.setMaxAssociations(Integer.valueOf(next.getValue()));
              } else if (attrName.equals("auto")) {
                group.setAuto(Boolean.valueOf(next.getValue()));
              }
              currentAssociation.addGroups(group);
            }
          }
        }
      }

    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (XMLStreamException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  /**
   * Handles Multi Channel Encapsulation message. Decapsulates an Application Command message and
   * handles it using the right endpoint.
   *
   * @param serialMessage the serial message to process.
   * @param offset the offset at which to start processing.
   */
  private void handleMultiChannelEncapResponse(SerialMessage serialMessage, int offset) {
    logger.trace("Process Multi-channel Encapsulation");

    if (serialMessage.getMessagePayload().length < offset + 2) {
      logger.error(
          "NODE {}: Invalid data length ({}/{})",
          this.getNode().getNodeId(),
          serialMessage.getMessagePayload().length,
          offset);
      return;
    }

    CommandClass commandClass;
    ZWaveCommandClass zwaveCommandClass;
    int endpointId = serialMessage.getMessagePayloadByte(offset);
    int commandClassCode = serialMessage.getMessagePayloadByte(offset + 2);
    commandClass = CommandClass.getCommandClass(commandClassCode);

    if (commandClass == null) {
      logger.error(
          String.format(
              "NODE %d: Unsupported command class 0x%02x",
              this.getNode().getNodeId(), commandClassCode));
      return;
    }

    logger.debug(
        String.format(
            "NODE %d: Requested Command Class = %s (0x%02x)",
            this.getNode().getNodeId(), commandClass.getLabel(), commandClassCode));
    ZWaveEndpoint endpoint = this.endpoints.get(endpointId);

    if (endpoint == null) {
      logger.error(
          "NODE {}: Endpoint {} not found. Cannot set command classes.",
          this.getNode().getNodeId(),
          endpointId);
      return;
    }

    zwaveCommandClass = endpoint.getCommandClass(commandClass);

    if (zwaveCommandClass == null) {
      logger.warn(
          String.format(
              "NODE %d: CommandClass %s (0x%02x) not implemented by endpoint %d, fallback to main node.",
              this.getNode().getNodeId(), commandClass.getLabel(), commandClassCode, endpointId));
      zwaveCommandClass = this.getNode().getCommandClass(commandClass);
    }

    if (zwaveCommandClass == null) {
      logger.error(
          String.format(
              "NODE %d: CommandClass %s (0x%02x) not implemented.",
              this.getNode().getNodeId(), commandClass.getLabel(), commandClassCode));
      return;
    }

    logger.debug(
        "NODE {}: Endpoint = {}, calling handleApplicationCommandRequest.",
        this.getNode().getNodeId(),
        endpointId);
    zwaveCommandClass.handleApplicationCommandRequest(serialMessage, offset + 3, endpointId);
  }
  /**
   * Handles Multi Channel Encapsulation message. Decapsulates an Application Command message and
   * handles it using the right endpoint.
   *
   * @param serialMessage the serial message to process.
   * @param offset the offset at which to start processing.
   * @throws ZWaveSerialMessageException
   */
  private void handleMultiChannelEncapResponse(SerialMessage serialMessage, int offset)
      throws ZWaveSerialMessageException {
    logger.trace("Process Multi-channel Encapsulation");

    if (serialMessage.getMessagePayload().length < offset + 2) {
      logger.error("NODE {}: Invalid data length", this.getNode().getNodeId());
      return;
    }

    CommandClass commandClass;
    ZWaveCommandClass zwaveCommandClass;
    int originatingEndpointId = serialMessage.getMessagePayloadByte(offset);

    {
      int destinationEndpointId = serialMessage.getMessagePayloadByte(offset + 1);

      if (destinationEndpointId != 1 && originatingEndpointId == 1) {
        logger.debug(
            "NODE {}: Controller has no endpoints. Probably originating ({}) and destination ({}) endpoints should be swapped.",
            this.getNode().getNodeId(),
            originatingEndpointId,
            destinationEndpointId);
        // not a full swap. Do not use destinationEndpointId after this line
        // and leave scope intact.
        originatingEndpointId = destinationEndpointId;
      }
    }

    int commandClassCode = serialMessage.getMessagePayloadByte(offset + 2);
    commandClass = CommandClass.getCommandClass(commandClassCode);

    if (commandClass == null) {
      logger.error(
          String.format(
              "NODE %d: Unsupported command class 0x%02x",
              this.getNode().getNodeId(), commandClassCode));
      return;
    }

    logger.debug(
        String.format(
            "NODE %d: Requested Command Class = %s (0x%02x)",
            this.getNode().getNodeId(), commandClass.getLabel(), commandClassCode));
    ZWaveEndpoint endpoint = this.endpoints.get(originatingEndpointId);

    if (endpoint == null) {
      logger.error(
          "NODE {}: Endpoint {} not found. Cannot set command classes.",
          this.getNode().getNodeId(),
          originatingEndpointId);
      return;
    }

    zwaveCommandClass = endpoint.getCommandClass(commandClass);

    if (zwaveCommandClass == null) {
      logger.warn(
          String.format(
              "NODE %d: CommandClass %s (0x%02x) not implemented by endpoint %d, fallback to main node.",
              this.getNode().getNodeId(),
              commandClass.getLabel(),
              commandClassCode,
              originatingEndpointId));
      zwaveCommandClass = this.getNode().getCommandClass(commandClass);
    }

    if (zwaveCommandClass == null) {
      logger.error(
          String.format(
              "NODE %d: CommandClass %s (0x%02x) not implemented.",
              this.getNode().getNodeId(), commandClass.getLabel(), commandClassCode));
      return;
    }

    logger.debug(
        "NODE {}: Endpoint = {}, calling handleApplicationCommandRequest.",
        this.getNode().getNodeId(),
        originatingEndpointId);
    zwaveCommandClass.handleApplicationCommandRequest(
        serialMessage, offset + 3, originatingEndpointId);
  }