void BootNormal::_mqttCallback(char* topic, char* payload) {
  String message = String(payload);
  String unified = String(topic);
  unified.remove(0, strlen(Config.get().mqtt.baseTopic) + strlen(Config.get().deviceId) + 1); // Remove devices/${id}/ --- +1 for /

  // Device properties
  if (Config.get().ota.enabled && unified == "$ota") {
    if (message != this->_interface->firmware.version) {
      Logger.log(F("✴ OTA available (version "));
      Logger.log(message);
      Logger.logln(F(")"));
      if (strlen(payload) + 1 <= MAX_FIRMWARE_VERSION_LENGTH) {
        strcpy(this->_otaVersion, payload);
        this->_flaggedForOta = true;
        Logger.logln(F("Flagged for OTA"));
      } else {
        Logger.logln(F("Version string received is too long"));
      }
    }
    return;
  } else if (unified == "$reset" && message == "true") {
    this->_fillMqttTopic(PSTR("/$reset"));
    MqttClient.publish("false", true);
    this->_flaggedForReset = true;
    Logger.logln(F("Flagged for reset by network"));
    return;
  }

  // Implicit node properties
  unified.remove(unified.length() - 4, 4); // Remove /set
  unsigned int separator = 0;
  for (unsigned int i = 0; i < unified.length(); i++) {
    if (unified.charAt(i) == '/') {
      separator = i;
      break;
    }
  }
  String node = unified.substring(0, separator);
  String property = unified.substring(separator + 1);

  int homieNodeIndex = -1;
  for (int i = 0; i < this->_interface->registeredNodesCount; i++) {
    const HomieNode* homieNode = this->_interface->registeredNodes[i];
    if (node == homieNode->getId()) {
      homieNodeIndex = i;
      break;
    }
  }

  if (homieNodeIndex == -1) {
    Logger.log(F("Node "));
    Logger.log(node);
    Logger.logln(F(" not registered"));
    return;
  }

  const HomieNode* homieNode = this->_interface->registeredNodes[homieNodeIndex];

  int homieNodePropertyIndex = -1;
  for (int i = 0; i < homieNode->getSubscriptionsCount(); i++) {
    Subscription subscription = homieNode->getSubscriptions()[i];
    if (property == subscription.property) {
      homieNodePropertyIndex = i;
      break;
    }
  }

  if (!homieNode->getSubscribeToAll() && homieNodePropertyIndex == -1) {
    Logger.log(F("Node "));
    Logger.log(node);
    Logger.log(F(" not subscribed to "));
    Logger.logln(property);
    return;
  }

  Logger.logln(F("Calling global input handler..."));
  bool handled = this->_interface->globalInputHandler(node, property, message);
  if (handled) return;

  Logger.logln(F("Calling node input handler..."));
  handled = homieNode->getInputHandler()(property, message);
  if (handled) return;

  if (homieNodePropertyIndex != -1) { // might not if subscribed to all only
    Subscription homieNodeSubscription = homieNode->getSubscriptions()[homieNodePropertyIndex];
    Logger.logln(F("Calling property input handler..."));
    handled = homieNodeSubscription.inputHandler(message);
  }

  if (!handled){
    Logger.logln(F("No handlers handled the following packet:"));
    Logger.log(F("  • Node ID: "));
    Logger.logln(node);
    Logger.log(F("  • Property: "));
    Logger.logln(property);
    Logger.log(F("  • Value: "));
    Logger.logln(message);
  }
}
void BootNormal::_onMqttMessage(char* topic, char* payload, uint8_t qos, size_t len, size_t index, size_t total) {
  if (total == 0) return; // no empty message possible

  topic = topic + strlen(_interface->config->get().mqtt.baseTopic) + strlen(_interface->config->get().deviceId) + 1; // Remove devices/${id}/ --- +1 for /

  if (strcmp_P(topic, PSTR("$ota/payload")) == 0) { // If this is the $ota payload
    if (_flaggedForOta) {
      if (index == 0) {
        Update.begin(total);
        _interface->logger->logln(F("OTA started"));
        _interface->logger->logln(F("Triggering HOMIE_OTA_STARTED event..."));
        _interface->eventHandler(HOMIE_OTA_STARTED);
      }
      _interface->logger->log(F("Receiving OTA payload ("));
      _interface->logger->log(index + len);
      _interface->logger->log(F("/"));
      _interface->logger->log(total);
      _interface->logger->logln(F(")..."));

      Update.write((uint8_t*)payload, len);

      if (index + len == total) {
        bool success = Update.end();

        if (success) {
          _interface->logger->logln(F("✔ OTA success"));
          _interface->logger->logln(F("Triggering HOMIE_OTA_SUCCESSFUL event..."));
          _interface->eventHandler(HOMIE_OTA_SUCCESSFUL);
          _flaggedForReboot = true;
        } else {
          _interface->logger->logln(F("✖ OTA failed"));
          _interface->logger->logln(F("Triggering HOMIE_OTA_FAILED event..."));
          _interface->eventHandler(HOMIE_OTA_FAILED);
        }

        _flaggedForOta = false;
        _interface->mqttClient->unsubscribe(_prefixMqttTopic(PSTR("/$ota/payload")));
      }
    } else {
      _interface->logger->log(F("Receiving OTA payload but not requested, skipping..."));
    }
    return;
  }

  if (_mqttPayloadBuffer == nullptr) _mqttPayloadBuffer = std::unique_ptr<char[]>(new char[total + 1]);

  memcpy(_mqttPayloadBuffer.get() + index, payload, len);

  if (index + len != total) return;
  _mqttPayloadBuffer.get()[total] = '\0';

  if (strcmp_P(topic, PSTR("$ota")) == 0) { // If this is the $ota announcement
    if (strcmp(_mqttPayloadBuffer.get(), _interface->firmware.version) != 0) {
      _interface->logger->log(F("✴ OTA available (version "));
      _interface->logger->log(_mqttPayloadBuffer.get());
      _interface->logger->logln(F(")"));

      _interface->logger->logln(F("Subscribing to OTA payload..."));
      _interface->mqttClient->subscribe(_prefixMqttTopic(PSTR("/$ota/payload")), 0);
      _flaggedForOta = true;
    }

    return;
  }

  if (strcmp_P(topic, PSTR("$reset")) == 0 && strcmp(_mqttPayloadBuffer.get(), "true") == 0) {
    _interface->mqttClient->publish(_prefixMqttTopic(PSTR("/$reset")), 1, true, "false");
    _flaggedForReset = true;
    _interface->logger->logln(F("Flagged for reset by network"));
    return;
  }

  // Implicit node properties
  topic[strlen(topic) - 4] = '\0'; // Remove /set
  uint16_t separator = 0;
  for (uint16_t i = 0; i < strlen(topic); i++) {
    if (topic[i] == '/') {
      separator = i;
      break;
    }
  }
  char* node = topic;
  node[separator] = '\0';
  char* property = topic + separator + 1;
  HomieNode* homieNode = HomieNode::find(node);
  if (!homieNode) {
    _interface->logger->log(F("Node "));
    _interface->logger->log(node);
    _interface->logger->logln(F(" not registered"));
    return;
  }

  Subscription subscription;
  bool subscriptionFound = false;
  for (Subscription iSubscription : homieNode->getSubscriptions()) {
    if (strcmp(property, iSubscription.property) == 0) {
      subscription = iSubscription;
      subscriptionFound = true;
      break;
    }
  }

  if (!homieNode->isSubscribedToAll() && !subscriptionFound) {
    _interface->logger->log(F("Node "));
    _interface->logger->log(node);
    _interface->logger->log(F(" not subscribed to "));
    _interface->logger->logln(property);
    return;
  }

  _interface->logger->logln(F("Calling global input handler..."));
  bool handled = _interface->globalInputHandler(String(node), String(property), String(_mqttPayloadBuffer.get()));
  if (handled) return;

  _interface->logger->logln(F("Calling node input handler..."));
  handled = homieNode->handleInput(String(property), String(_mqttPayloadBuffer.get()));
  if (handled) return;

  if (subscriptionFound) { // might not if subscribed to all only
    _interface->logger->logln(F("Calling property input handler..."));
    handled = subscription.inputHandler(String(_mqttPayloadBuffer.get()));
  }

  if (!handled){
    _interface->logger->logln(F("No handlers handled the following packet:"));
    _interface->logger->log(F("  • Node ID: "));
    _interface->logger->logln(node);
    _interface->logger->log(F("  • Property: "));
    _interface->logger->logln(property);
    _interface->logger->log(F("  • Value: "));
    _interface->logger->logln(_mqttPayloadBuffer.get());
  }
}