/** {@inheritDoc} */
  @Override
  public void internalReceiveCommand(String itemName, Command command) {
    logger.debug("Received command from {}", itemName);

    // resolve serial number for item
    String serialNumber = null;

    for (MaxCubeBindingProvider provider : providers) {
      serialNumber = provider.getSerialNumber(itemName);

      if (StringUtils.isBlank(serialNumber)) {
        continue;
      }

      // send command to MAX!Cube LAN Gateway
      Device device = findDevice(serialNumber, devices);

      if (device == null) {
        logger.debug(
            "Cannot send command to device with serial number {}, device not listed.",
            serialNumber);
        continue;
      }

      String rfAddress = device.getRFAddress();
      String commandString = null;

      if (command instanceof DecimalType || command instanceof OnOffType) {
        DecimalType decimalType = DEFAULT_OFF_TEMPERATURE;
        if (command instanceof DecimalType) {
          decimalType = (DecimalType) command;
        } else if (command instanceof OnOffType) {
          decimalType =
              OnOffType.ON.equals(command) ? DEFAULT_ON_TEMPERATURE : DEFAULT_OFF_TEMPERATURE;
        }

        S_Command cmd =
            new S_Command(
                rfAddress,
                device.getRoomId(),
                ((HeatingThermostat) device).getMode(),
                decimalType.doubleValue());
        commandString = cmd.getCommandString();
      } else if (command instanceof StringType) {
        String commandContent = command.toString().trim().toUpperCase();
        S_Command cmd = null;
        ThermostatModeType commandThermoType = null;
        if (commandContent.contentEquals(ThermostatModeType.AUTOMATIC.toString())) {
          commandThermoType = ThermostatModeType.AUTOMATIC;
          cmd = new S_Command(rfAddress, device.getRoomId(), commandThermoType);
        } else if (commandContent.contentEquals(ThermostatModeType.BOOST.toString())) {
          commandThermoType = ThermostatModeType.BOOST;
          Double setTemp =
              Double.parseDouble(((HeatingThermostat) device).getTemperatureSetpoint().toString());
          cmd = new S_Command(rfAddress, device.getRoomId(), commandThermoType, setTemp);
        } else if (commandContent.contentEquals(ThermostatModeType.MANUAL.toString())) {
          commandThermoType = ThermostatModeType.MANUAL;
          Double setTemp =
              Double.parseDouble(((HeatingThermostat) device).getTemperatureSetpoint().toString());
          cmd = new S_Command(rfAddress, device.getRoomId(), commandThermoType, setTemp);
          logger.debug("updates to MANUAL mode with temperature '{}'", setTemp);
        } else {
          logger.debug(
              "Only updates to AUTOMATIC, MANUAL & BOOST supported, received value ;'{}'",
              commandContent);
          continue;
        }
        commandString = cmd.getCommandString();
      }

      if (commandString != null) {

        try {
          if (socket == null) {
            this.socketConnect();
          }
          writer.write(commandString);
          logger.debug(commandString);
          writer.flush();

          Message message = null;
          String raw = reader.readLine();
          try {
            while (!this.messageProcessor.isMessageAvailable()) {
              this.messageProcessor.addReceivedLine(raw);
              raw = reader.readLine();
            }

            message = this.messageProcessor.pull();
          } catch (Exception e) {
            logger.info("Error while handling response from MAX! Cube lan gateway!");
            logger.debug(Utils.getStackTrace(e));
            this.messageProcessor.reset();
          }

          if (message != null) {
            if (message.getType() == MessageType.S) {
              sMessageProcessing((S_Message) message);
            }
          }
          if (!exclusive) {
            socket.close();
            socket = null;
          }
        } catch (UnknownHostException e) {
          logger.info(
              "Host error occurred while connecting to MAX! Cube lan gateway '{}': {}",
              ip,
              e.getMessage());
          socketClose();
        } catch (IOException e) {
          logger.info(
              "IO error occurred while writing to MAX! Cube lan gateway '{}': {}",
              ip,
              e.getMessage());
          socketClose(); // reconnect on next execution
        } catch (Exception e) {
          logger.info(
              "Error occurred while writing to MAX! Cube lan gateway '{}': {}", ip, e.getMessage());
          logger.info(Utils.getStackTrace(e));
          socketClose(); // reconnect on next execution
        }
        logger.debug("Command Sent to {}", ip);
      } else {
        logger.debug("Null Command not sent to {}", ip);
      }
    }
  }
  /** {@inheritDoc} */
  @Override
  public synchronized void execute() {
    if (ip == null) {
      logger.debug("Update prior to completion of interface IP configuration");
      return;
    }
    try {
      String raw = null;
      if (maxRequestsPerConnection > 0 && requestCount >= maxRequestsPerConnection) {
        logger.debug("maxRequestsPerConnection reached, reconnecting.");
        socket.close();
        this.socketConnect();
      }
      if (socket == null) {
        this.socketConnect();
      } else {

        /*
         * if the connection is already open (this happens in exclusive mode), just send a "l:\r\n" to get the
         * latest live informations
         * note that "L:\r\n" or "l:\n" would not work.
         */
        logger.debug("Sending state request #" + this.requestCount + " to Maxcube");
        writer.write("l:" + '\r' + '\n');
        writer.flush();
        requestCount++;
      }

      boolean cont = true;
      while (cont) {
        raw = reader.readLine();
        if (raw == null) {
          cont = false;
          continue;
        }

        Message message = null;
        try {
          this.messageProcessor.addReceivedLine(raw);
          if (this.messageProcessor.isMessageAvailable()) {
            message = this.messageProcessor.pull();
          } else {
            continue;
          }

          message.debug(logger);

          if (message != null) {
            message.debug(logger);
            if (message.getType() == MessageType.M) {
              M_Message msg = (M_Message) message;
              for (DeviceInformation di : msg.devices) {
                Configuration c = null;
                for (Configuration conf : configurations) {
                  if (conf.getSerialNumber().equalsIgnoreCase(di.getSerialNumber())) {
                    c = conf;
                    break;
                  }
                }

                if (c != null) {
                  configurations.remove(c);
                }

                c = Configuration.create(di);
                configurations.add(c);

                c.setRoomId(di.getRoomId());
              }
            } else if (message.getType() == MessageType.C) {
              Configuration c = null;
              for (Configuration conf : configurations) {
                if (conf.getSerialNumber()
                    .equalsIgnoreCase(((C_Message) message).getSerialNumber())) {
                  c = conf;
                  break;
                }
              }

              if (c == null) {
                configurations.add(Configuration.create(message));
              } else {
                c.setValues((C_Message) message);
              }
            } else if (message.getType() == MessageType.S) {
              sMessageProcessing((S_Message) message);
              cont = false;
            } else if (message.getType() == MessageType.L) {
              ((L_Message) message).updateDevices(devices, configurations);

              logger.debug("{} devices found.", devices.size());

              // the L message is the last one, while the reader
              // would hang trying to read a new line and
              // eventually the
              // cube will fail to establish
              // new connections for some time
              cont = false;
            }
          }
        } catch (IncorrectMultilineIndexException ex) {
          logger.info(
              "Incorrect MAX!Cube multiline message detected. Stopping processing and continue with next Line.");
          this.messageProcessor.reset();
        } catch (NoMessageAvailableException ex) {
          logger.info(
              "Could not process MAX!Cube message. Stopping processing and continue with next Line.");
          this.messageProcessor.reset();
        } catch (IncompleteMessageException ex) {
          logger.info(
              "Error while parsing MAX!Cube multiline message. Stopping processing, and continue with next Line.");
          this.messageProcessor.reset();
        } catch (UnprocessableMessageException ex) {
          logger.info(
              "Error while parsing MAX!Cube message. Stopping processing, and continue with next Line.");
          this.messageProcessor.reset();
        } catch (UnsupportedMessageTypeException ex) {
          logger.info(
              "Unsupported MAX!Cube message detected. Ignoring and continue with next Line.");
          this.messageProcessor.reset();
        } catch (MessageIsWaitingException ex) {
          logger.info(
              "There was and unhandled message waiting. Ignoring and continue with next Line.");
          this.messageProcessor.reset();
        } catch (Exception e) {
          logger.info("Failed to process message received by MAX! protocol.");
          logger.debug(Utils.getStackTrace(e));
          this.messageProcessor.reset();
        }
      }
      if (!exclusive) {
        socketClose();
      }

      for (MaxCubeBindingProvider provider : providers) {
        for (String itemName : provider.getItemNames()) {
          String serialNumber = provider.getSerialNumber(itemName);

          Device device = findDevice(serialNumber, devices);

          if (device == null) {
            logger.info("Cannot find MAX!cube device with serial number '{}'", serialNumber);
            logAvailableMaxDevices();
            continue;
          }
          // all devices have a battery state, so this is type-independent
          if (provider.getBindingType(itemName) == BindingType.BATTERY) {
            if (device.battery().isChargeUpdated()) {
              eventPublisher.postUpdate(itemName, device.battery().getCharge());
            }
          } else if (provider.getBindingType(itemName) == BindingType.CONNECTION_ERROR) {
            if (device.isErrorUpdated()) {
              OnOffType connectionError = device.isError() ? OnOffType.ON : OnOffType.OFF;
              eventPublisher.postUpdate(itemName, connectionError);
            }
          } else {
            switch (device.getType()) {
              case HeatingThermostatPlus:
              case HeatingThermostat:
                if (provider.getBindingType(itemName) == BindingType.VALVE
                    && ((HeatingThermostat) device).isValvePositionUpdated()) {
                  eventPublisher.postUpdate(
                      itemName, ((HeatingThermostat) device).getValvePosition());
                  break;
                }
                // omitted break, fall through
              case WallMountedThermostat: // and also HeatingThermostat
                if (provider.getBindingType(itemName) == BindingType.MODE
                    && ((HeatingThermostat) device).isModeUpdated()) {
                  eventPublisher.postUpdate(itemName, ((HeatingThermostat) device).getModeString());
                } else if (provider.getBindingType(itemName) == BindingType.ACTUAL
                    && ((HeatingThermostat) device).isTemperatureActualUpdated()) {
                  eventPublisher.postUpdate(
                      itemName, ((HeatingThermostat) device).getTemperatureActual());
                } else if (((HeatingThermostat) device).isTemperatureSetpointUpdated()
                    && provider.getBindingType(itemName) == null) {
                  eventPublisher.postUpdate(
                      itemName, ((HeatingThermostat) device).getTemperatureSetpoint());
                }
                break;
              case ShutterContact:
                if (((ShutterContact) device).isShutterStateUpdated()) {
                  eventPublisher.postUpdate(itemName, ((ShutterContact) device).getShutterState());
                }
                break;
              default:
                // no further devices supported yet
            }
          }
        }
      }
    } catch (UnknownHostException e) {
      logger.info(
          "Host error occurred while connecting to MAX! Cube lan gateway '{}': {}",
          ip,
          e.getMessage());
      socketClose();
    } catch (IOException e) {
      logger.info(
          "IO error occurred while connecting to MAX! Cube lan gateway '{}': {}",
          ip,
          e.getMessage());
      socketClose(); // reconnect on next execution
    } catch (Exception e) {
      logger.info(
          "Error occurred while connecting to MAX! Cube lan gateway '{}': {}", ip, e.getMessage());
      logger.info(Utils.getStackTrace(e));
      socketClose(); // reconnect on next execution
    }
  }