/**
   * Gets a SerialMessage with the THERMOSTAT_SETPOINT_GET command
   *
   * @param setpointType the setpoint type to get
   * @return the serial message
   */
  public SerialMessage getMessage(SetpointType setpointType) {
    if (setpointType == null) {
      return null;
    }

    logger.debug(
        "NODE {}: Creating new message for application command THERMOSTAT_SETPOINT_GET",
        this.getNode().getNodeId());
    SerialMessage result =
        new SerialMessage(
            this.getNode().getNodeId(),
            SerialMessageClass.SendData,
            SerialMessageType.Request,
            SerialMessageClass.SendData,
            SerialMessagePriority.Get);
    byte[] payload = {
      (byte) this.getNode().getNodeId(),
      3,
      (byte) getCommandClass().getKey(),
      THERMOSTAT_SETPOINT_GET,
      (byte) setpointType.getKey()
    };
    result.setMessagePayload(payload);
    return result;
  }
  /**
   * Processes a THERMOSTAT_SETPOINT_REPORT message.
   *
   * @param serialMessage the incoming message to process.
   * @param offset the offset position from which to start message processing.
   * @param endpoint the endpoint or instance number this message is meant for.
   */
  protected void processThermostatSetpointReport(
      SerialMessage serialMessage, int offset, int endpoint) {

    int setpointTypeCode = serialMessage.getMessagePayloadByte(offset + 1);
    int scale = (serialMessage.getMessagePayloadByte(offset + 2) >> 3) & 0x03;

    try {
      BigDecimal value = extractValue(serialMessage.getMessagePayload(), offset + 2);

      logger.debug(
          "NODE {}: Thermostat Setpoint report Scale = {}", this.getNode().getNodeId(), scale);
      logger.debug("NODE {}: Thermostat Setpoint Value = {}", this.getNode().getNodeId(), value);

      SetpointType setpointType = SetpointType.getSetpointType(setpointTypeCode);

      if (setpointType == null) {
        logger.error(
            "NODE {}: Unknown Setpoint Type = {}, ignoring report.",
            this.getNode().getNodeId(),
            setpointTypeCode);
        return;
      }

      // setpoint type seems to be supported, add it to the list.
      if (!this.setpointTypes.contains(setpointType)) this.setpointTypes.add(setpointType);

      logger.debug(
          "NODE {}: Setpoint Type = {} ({})",
          this.getNode().getNodeId(),
          setpointType.getLabel(),
          setpointTypeCode);

      logger.debug(
          "NODE {}: Thermostat Setpoint Report value = {}",
          this.getNode().getNodeId(),
          value.toPlainString());
      ZWaveThermostatSetpointValueEvent zEvent =
          new ZWaveThermostatSetpointValueEvent(
              this.getNode().getNodeId(), endpoint, setpointType, scale, value);
      this.getController().notifyEventListeners(zEvent);
    } catch (NumberFormatException e) {
      return;
    }
  }
  /**
   * Gets a SerialMessage with the THERMOSTAT_SETPOINT_SET command
   *
   * @param scale the scale (DegC or DegF)
   * @param setpointType the setpoint type to set
   * @param setpoint the setpoint to set.
   * @return the serial message
   */
  public SerialMessage setMessage(int scale, SetpointType setpointType, BigDecimal setpoint) {
    logger.debug(
        "NODE {}: Creating new message for application command THERMOSTAT_SETPOINT_SET",
        this.getNode().getNodeId());
    SerialMessage result =
        new SerialMessage(
            this.getNode().getNodeId(),
            SerialMessageClass.SendData,
            SerialMessageType.Request,
            SerialMessageClass.SendData,
            SerialMessagePriority.Set);

    try {
      byte[] encodedValue = encodeValue(setpoint);

      byte[] payload =
          ArrayUtils.addAll(
              new byte[] {
                (byte) this.getNode().getNodeId(),
                (byte) (3 + encodedValue.length),
                (byte) getCommandClass().getKey(),
                THERMOSTAT_SETPOINT_SET,
                (byte) setpointType.getKey()
              },
              encodedValue);
      // Add the scale
      payload[5] += (byte) (scale << 3);

      result.setMessagePayload(payload);
      return result;
    } catch (ArithmeticException e) {
      logger.error(
          "NODE {}: Got an arithmetic exception converting value {} to a valid Z-Wave value. Ignoring THERMOSTAT_SETPOINT_SET message.",
          this.getNode().getNodeId(),
          setpoint);
      return null;
    }
  }
  /** {@inheritDoc} */
  @Override
  public void handleApplicationCommandRequest(
      SerialMessage serialMessage, int offset, int endpoint) {
    logger.debug("NODE {}: Received Thermostat Setpoint Request", this.getNode().getNodeId());
    int command = serialMessage.getMessagePayloadByte(offset);
    switch (command) {
      case THERMOSTAT_SETPOINT_SET:
      case THERMOSTAT_SETPOINT_GET:
      case THERMOSTAT_SETPOINT_SUPPORTED_GET:
        logger.warn("NODE {}: Command {} not implemented.", this.getNode().getNodeId(), command);
        return;
      case THERMOSTAT_SETPOINT_SUPPORTED_REPORT:
        logger.debug(
            "NODE {}: Process Thermostat Supported Setpoint Report", this.getNode().getNodeId());

        int payloadLength = serialMessage.getMessagePayload().length;

        for (int i = offset + 1; i < payloadLength; ++i) {
          int bitMask = serialMessage.getMessagePayloadByte(i);
          for (int bit = 0; bit < 8; ++bit) {
            if ((bitMask & (1 << bit)) == 0) continue;

            int index = ((i - (offset + 1)) * 8) + bit;
            if (index >= SetpointType.values().length) continue;

            // (n)th bit is set. n is the index for the setpoint type enumeration.
            SetpointType setpointTypeToAdd = SetpointType.getSetpointType(index);

            if (setpointTypeToAdd == null) {
              logger.warn(
                  "NODE {}: Unknown Setpoint Type = {}, ignoring report.",
                  this.getNode().getNodeId(),
                  index);
              return;
            }

            this.setpointTypes.add(setpointTypeToAdd);
            logger.debug(
                "NODE {}: Added setpoint type {} {}",
                this.getNode().getNodeId(),
                setpointTypeToAdd.getLabel(),
                index);
          }
        }

        this.getNode().advanceNodeStage(NodeStage.DYNAMIC);
        break;
      case THERMOSTAT_SETPOINT_REPORT:
        logger.trace("NODE {}: Process Thermostat Setpoint Report", this.getNode().getNodeId());
        processThermostatSetpointReport(serialMessage, offset, endpoint);

        if (this.getNode().getNodeStage() != NodeStage.DONE)
          this.getNode().advanceNodeStage(NodeStage.DONE);

        break;
      default:
        logger.warn(
            "NODE {}: Unsupported Command {} for command class {} ({}).",
            this.getNode().getNodeId(),
            command,
            this.getCommandClass().getLabel(),
            this.getCommandClass().getKey());
    }
  }