Can someone please explain MQTT in HA

I quite struggled with defining a device, too - so I thought sharing my current solution for a device containing multiple entities and autodetection could be helpful to someone.

Currently my sensor contains a LDR and the BME280 (temperature, humidity, pressure)

Here just the relevant code:

/************* MQTT TOPICS  **************************/
const string SENSOR_ID {"sn01"};

const string state_topic{"homeassistant/sensor/" + SENSOR_ID + "/state"};
const string set_topic{"homeassistant/sensor/" + SENSOR_ID + "/set"};

// {<entityId>, <entityUnit>, <entityClass>}
const vector<vector<string>> ENTITIES{
    {"ldr", "%", "illuminance"},
    {"temp", "°C", "temperature"},
    {"hum", "%", "humidity"},
    {"pres", "hPa", "pressure"},
};

// ...

void sendState()
{
  // Save the last time a new reading was published
  lastPublishMillis = millis();

  DynamicJsonDocument configPayload(1024);

  //////////////////////////////////////////////////////
  ///////// Shared config for every entity /////////////
  //////////////////////////////////////////////////////

  configPayload["stat_t"] = state_topic.c_str();
  configPayload["expire_after"] = EXPIRE_AFTER;
  configPayload["dev"]["name"] = SENSOR_ID.c_str();
  configPayload["dev"]["model"] = "NodeMCU";
  configPayload["dev"]["manufacturer"] = "Manufacturer";
  JsonArray identifiers{configPayload["dev"].createNestedArray("ids")};
  identifiers.add(SENSOR_ID.c_str());

  //////////////////////////////////////////////////////
  ///////// Setting the config for each entity /////////
  //////////////////////////////////////////////////////

  for (auto entity : ENTITIES)
  {
    const string unique_id {SENSOR_ID + "_" + entity.at(0)};
    const string value_template {"{{value_json['" + entity.at(0)+ "']}}"};

    configPayload["name"] = unique_id.c_str();
    configPayload["uniq_id"] = unique_id.c_str();
    configPayload["val_tpl"] = value_template.c_str();
    configPayload["unit_of_meas"] = entity.at(1).c_str();
    configPayload["device_class"] = entity.at(2).c_str();

    const string config_topic_entity{"homeassistant/sensor/" + SENSOR_ID + "_" + entity.at(0) + "/config"};

    char configPayloadSerialized[1024]{};
    serializeJson(configPayload, configPayloadSerialized);
    Serial.println(config_topic_entity.c_str());
    Serial.println(configPayloadSerialized);

    // Publishing
    bool publishSuccessful {0};
    if (client.publish(config_topic_entity.c_str(), configPayloadSerialized, true))
      publishSuccessful = 1;

    const string publishMessage {
      "config " + entity[0] + (publishSuccessful ? " " : " NOT ") + "published\n"
    };
    Serial.println(publishMessage.c_str());
  }

  ////////////////////////////////////
  //////// updating the state ////////
  ////////////////////////////////////

  // Inverting value to receive bright = high numbers + converting to %.
  const unsigned short LDRPIN_MAX{1024};
  const float PERCENTAGE_DIVIDER{10.24};
  LDR = (LDRPIN_MAX - analogRead(LDRPIN)) / PERCENTAGE_DIVIDER;

  // New BME280 sensor readings
  // Celsius:
  temp = bme.readTemperature();
  // Fahrenheit:
  //temp = 1.8*bme.readTemperature() + 32;
  hum = bme.readHumidity();
  pres = bme.readPressure() / 100.0F;

  DynamicJsonDocument sensorData(1024);
  sensorData["ldr"] = round(LDR*10)/10;
  sensorData["temp"] = round(temp*10)/10;
  sensorData["hum"] = round(hum*10)/10;
  sensorData["pres"] = round(pres*10)/10;

  char buffer[1024]{""};
  serializeJson(sensorData, buffer);
  Serial.println(buffer);

  if (client.publish(state_topic.c_str(), buffer, true))
    Serial.println("state published");
  else
    Serial.println("state NOT published");
  Serial.println();
}

Important for a device including multiple entities is that each entity has to be configured independently to the config_topic. Something like
homeassistant/sensor/<sensorId>_<entityName>/config
E.g. for the ldr:
homeassistant/sensor/sensornode1_ldr/config

Sending the state is then handled to the same stateTopic that’s defined in the entity’s json (in my example within “shared config”):
homeassistant/sensor/sensornode1/state

6 Likes