Mastering Home Assistant Data Transformation: Parsing Complex MQTT JSON Payloads with Templates

Represent Mastering Home Assistant Data Transformation: Parsing Complex MQTT JSON Payloads with Templates article
6m read

Introduction: Taming the Wild West of External Data

Integrating diverse devices and services into Home Assistant often means confronting a common challenge: data inconsistency. While many integrations present data cleanly, custom sensors, industrial gateways, or external APIs pushed via MQTT frequently deliver raw, complex JSON payloads. These payloads might contain nested structures, arrays, or multiple data points bundled together, making them challenging to use directly in your automations or Lovelace dashboards. You're not alone if you've struggled to extract a single temperature reading from a sprawling JSON blob.

This guide dives deep into Home Assistant's powerful templating engine, specifically focusing on how to leverage mqtt_sensor and template sensors to dissect, transform, and refine even the most convoluted MQTT JSON payloads. We'll move beyond basic value extraction to build robust, actionable entities from raw data, enabling more intelligent automations and clearer insights into your smart home ecosystem.

Step-by-Step Setup: The Foundation – Basic `mqtt_sensor` for JSON Attributes

Our journey begins with the mqtt_sensor. While it can directly extract a single value from a topic, its true power for complex JSON lies in the json_attributes_topic and value_template parameters. Let's assume you have a device publishing a JSON payload like this to home/sensor/mysensor/state:


{
  "temperature": 23.5,
  "humidity": 60.2,
  "pressure": 1012.3,
  "location": "living_room",
  "battery": {
    "level": 85,
    "status": "ok"
  },
  "read_time": "2023-10-27T10:30:00Z"
}

To get this data into Home Assistant, we'll start by making the entire JSON payload available as attributes of a primary sensor.


# configuration.yaml or sensors.yaml

- platform: mqtt
  name: "My Sensor Data"
  state_topic: "home/sensor/mysensor/state"
  value_template: "{{ value_json.temperature | default('unavailable') }}"
  unit_of_measurement: "°C"
  device_class: "temperature"
  json_attributes_topic: "home/sensor/mysensor/state"

This configuration creates an entity sensor.my_sensor_data whose state will be the temperature. Crucially, json_attributes_topic makes the entire JSON payload available as attributes of this sensor. You can verify this by checking the sensor's state in Developer Tools (/developer-tools/state).

Step-by-Step Setup: Extracting Core State with `value_template`

Sometimes, the primary state of your mqtt_sensor needs to be a specific value from the JSON. The value_template uses Jinja2 templating to pick out precisely what you need. Let's say you want the primary sensor state to be the humidity.


# configuration.yaml or sensors.yaml

- platform: mqtt
  name: "My Sensor Humidity"
  state_topic: "home/sensor/mysensor/state"
  value_template: "{{ value_json.humidity | default('unknown') }}" # Extract humidity
  unit_of_measurement: "%"
  device_class: "humidity"
  json_attributes_topic: "home/sensor/mysensor/state" # Still make all attributes available

Now, sensor.my_sensor_humidity will directly reflect the humidity value. The | default('unknown') filter is a crucial best practice for robustness, preventing template errors if the humidity key is ever missing from the payload.

Step-by-Step Setup: Advanced Transformation with `template` Sensors

What if you need to calculate a value from multiple attributes, apply conditional logic, or present data in a more user-friendly format that isn't directly available in the MQTT payload? This is where dedicated template sensors shine. They can read attributes from other entities and perform complex operations.

Let's create a template sensor to display the battery level and status more descriptively, and another to calculate a heat index if temperature and humidity meet certain criteria.


# configuration.yaml or sensors.yaml

# Template sensor for descriptive battery status
- platform: template
  sensors:
    my_sensor_battery_status:
      friendly_name: "My Sensor Battery Status"
      value_template: >
        {% set battery = state_attr('sensor.my_sensor_data', 'battery') %}
        {% if battery is not none and battery.level is defined %}
          Battery: {{ battery.level }}% ({{ battery.status | capitalize }})
        {% else %}
          Unknown
        {% endif %}
      icon_template: >
        {% set battery_level = state_attr('sensor.my_sensor_data', 'battery').level | int(0) %}
        {% if battery_level > 80 %}
          mdi:battery-full
        {% elif battery_level > 50 %}
          mdi:battery-half
        {% elif battery_level > 20 %}
          mdi:battery-low
        {% else %}
          mdi:battery-alert
        {% endif %}

# Template sensor for calculated heat index (simplified for example)
    my_sensor_heat_index:
      friendly_name: "My Sensor Heat Index"
      unit_of_measurement: "°C"
      device_class: "temperature"
      value_template: >
        {% set temp = states('sensor.my_sensor_data') | float %}
        {% set hum = state_attr('sensor.my_sensor_data', 'humidity') | float %}
        {% if temp > 25 and hum > 70 %}
          {{ (temp + (hum / 5)) | round(1) }} # Simplified calculation
        {% else %}
          {{ temp | round(1) }} # Just show temperature if conditions not met
        {% endif %}

These template sensors leverage the attributes parsed by sensor.my_sensor_data, demonstrating how you can transform raw JSON data into meaningful, derived entities within Home Assistant.

Troubleshooting Section

Parsing external data is prone to errors. Here's how to debug common issues:

  • Malformed JSON or MQTT Connection Issues:
    • Symptom: Sensor state is 'unavailable' or 'unknown', no attributes appear.
    • Fix: Use an MQTT client like MQTT Explorer to inspect the exact payload published to your MQTT topic (e.g., home/sensor/mysensor/state). Ensure it's valid JSON. Check your Home Assistant logs (/config/logs) for MQTT connection errors.
    • Tip: You can quickly validate JSON using online tools or a command-line utility like jq (echo 'your_json_string' | jq .).
  • Template Errors (e.g., 'UndefinedError', 'Nonetype' object is not subscriptable):
    • Symptom: Sensor state is 'unknown', or logs show Jinja2 errors. This usually means a key or attribute path in your template doesn't exist at the time of evaluation.
    • Fix: Go to Developer Tools -> Templates (/developer-tools/template). Paste your value_template or icon_template code there and test it with dummy data or by referencing the actual sensor's attributes (e.g., {{ state_attr('sensor.my_sensor_data', 'battery') }}). Use | default('') or is not none checks liberally to handle missing data gracefully.
    • Example: Instead of {{ value_json.nested.key }}, use {{ value_json.nested.key | default('N/A') }}. For attributes, {% set attr = state_attr('sensor.entity', 'attribute_name') %}{% if attr is not none %}...{% endif %}.
  • Incorrect Data Types:
    • Symptom: Calculations fail, or comparisons don't work as expected (e.g., '25' > 100 is true because it's comparing strings).
    • Fix: Always cast values to the correct type for comparisons or calculations using filters like | float, | int, or | bool.
    • Example: {% if states('sensor.my_sensor_data') | float > 25.0 %}

Advanced Config / Optimization: Handling Nested Arrays and Dynamic Keys

Sometimes, your JSON payload might contain arrays of objects, or keys that change dynamically. Home Assistant's templating can handle this, though it gets more complex.

Consider a payload with a list of historical readings:


{
  "device_id": "XYZ123",
  "readings": [
    {"timestamp": "2023-10-27T10:00:00Z", "temp": 23.0},
    {"timestamp": "2023-10-27T10:05:00Z", "temp": 23.2}
  ],
  "latest_reading": {"timestamp": "2023-10-27T10:30:00Z", "temp": 23.5}
}

To get the latest historical temperature (assuming the array is ordered latest first, or you want a specific index):


# Extract the temperature from the first item in the 'readings' array
- platform: mqtt
  name: "Historical Temp Latest"
  state_topic: "home/sensor/array_sensor/state"
  value_template: "{{ value_json.readings[0].temp | default('unavailable') }}"
  unit_of_measurement: "°C"
  device_class: "temperature"

For more complex scenarios, like averaging values from an array or searching for a specific item, you would use a template sensor with Jinja's for loops:


# Template sensor to calculate average of historical temperatures (simple example)
- platform: template
  sensors:
    historical_temp_average:
      friendly_name: "Historical Temperature Average"
      unit_of_measurement: "°C"
      device_class: "temperature"
      value_template: >
        {% set readings = state_attr('sensor.historical_temp_latest', 'readings') %}
        {% if readings is not none and readings | length > 0 %}
          {% set total_temp = 0 %}
          {% for item in readings %}
            {% set total_temp = total_temp + item.temp | float %}
          {% endfor %}
          {{ (total_temp / readings | length) | round(1) }}
        {% else %}
          unknown
        {% endif %}

Real-World Example: Integrating a Custom HVAC Monitor

Let's imagine you've built an ESP32-based HVAC monitor that publishes a detailed JSON payload via MQTT to hvac/monitor/data:


{
  "zone": "main_floor",
  "sensor_id": "HVAC_001",
  "metrics": {
    "supply_temp": 28.1,
    "return_temp": 23.7,
    "fan_speed_rpm": 1250,
    "filter_pressure_diff": 0.12,
    "current_state": "heating"
  },
  "errors": [
    {"code": 101, "message": "Sensor drift detected", "severity": "warning"}
  ],
  "uptime_minutes": 1440
}

Here's how you'd integrate this into Home Assistant, extracting key metrics and creating actionable entities:


# configuration.yaml

# Main MQTT Sensor to capture all data as attributes
- platform: mqtt
  name: "HVAC Main Monitor"
  state_topic: "hvac/monitor/data"
  value_template: "{{ value_json.metrics.current_state | default('off') }}"
  json_attributes_topic: "hvac/monitor/data"
  icon: mdi:hvac

# Template Sensors to expose individual metrics
- platform: template
  sensors:
    hvac_supply_temperature:
      friendly_name: "HVAC Supply Temperature"
      unit_of_measurement: "°C"
      device_class: "temperature"
      value_template: "{{ state_attr('sensor.hvac_main_monitor', 'metrics').supply_temp | float | round(1) | default('unknown') }}"

    hvac_return_temperature:
      friendly_name: "HVAC Return Temperature"
      unit_of_measurement: "°C"
      device_class: "temperature"
      value_template: "{{ state_attr('sensor.hvac_main_monitor', 'metrics').return_temp | float | round(1) | default('unknown') }}"

    hvac_fan_speed:
      friendly_name: "HVAC Fan Speed"
      unit_of_measurement: "RPM"
      icon_template: "mdi:fan"
      value_template: "{{ state_attr('sensor.hvac_main_monitor', 'metrics').fan_speed_rpm | int | default('unknown') }}"

    hvac_filter_status:
      friendly_name: "HVAC Filter Status"
      value_template: >
        {% set pressure = state_attr('sensor.hvac_main_monitor', 'metrics').filter_pressure_diff | float %}
        {% if pressure > 0.2 %}
          Replace Filter!
        {% elif pressure > 0.1 %}
          Check Filter Soon
        {% else %}
          Good
        {% endif %}
      icon_template: >
        {% set pressure = state_attr('sensor.hvac_main_monitor', 'metrics').filter_pressure_diff | float %}
        {% if pressure > 0.2 %}
          mdi:air-filter-warning
        {% elif pressure > 0.1 %}
          mdi:air-filter
        {% else %}
          mdi:air-filter-outline
        {% endif %}
      
    hvac_errors:
      friendly_name: "HVAC System Errors"
      value_template: >
        {% set errors = state_attr('sensor.hvac_main_monitor', 'errors') %}
        {% if errors is not none and errors | length > 0 %}
          {% for error in errors %}
            {{ error.code }}: {{ error.message }} ({{ error.severity | capitalize }})
            {% if not loop.last %}; {% endif %}
          {% endfor %}
        {% else %}
          No errors
        {% endif %}
      icon_template: >
        {% set errors = state_attr('sensor.hvac_main_monitor', 'errors') %}
        {% if errors is not none and errors | length > 0 %}
          mdi:alert-octagon
        {% else %}
          mdi:check-circle
        {% endif %}

This configuration creates 6 distinct entities from one complex MQTT payload: one primary sensor for the HVAC state, and five template sensors for specific metrics like supply temperature, return temperature, fan speed, filter status (derived), and a concatenated list of errors. This approach transforms a raw data stream into a highly usable and actionable set of Home Assistant entities.

Best Practices and Wrap-up

  • Robustness with default and is not none: Always anticipate missing keys or attributes. Use | default('fallback_value'), is defined, and is not none checks to prevent template errors that can make your sensors unavailable.
  • Type Casting: Explicitly cast values with | float, | int, or | bool before performing calculations or comparisons to ensure correct behavior.
  • Organization: For complex configurations, use !include directives to separate your MQTT and template sensor definitions into dedicated files (e.g., mqtt_sensors.yaml, template_sensors.yaml) within a sensors directory. This keeps your configuration.yaml clean and manageable.
  • Performance: While powerful, overly complex templates that update very frequently can consume CPU resources. Optimize your templates for efficiency, especially those involving loops or extensive string manipulation. Consider if some preprocessing can be done at the source (e.g., in your ESPHome device or Node-RED flow) before publishing to MQTT.
  • Testing: The Developer Tools -> Templates tab is your best friend for debugging. Test small parts of your template logic there before deploying to your configuration.
  • Security: Ensure your MQTT broker is properly secured (TLS, authentication) and avoid publishing sensitive information in plain text. If data must be secured end-to-end, consider encrypted MQTT or alternative secure transport methods.
  • Documentation: Add comments to your YAML configurations, explaining the logic of complex templates, especially if you're deriving values or handling edge cases. Your future self (or collaborators) will thank you.

By mastering Home Assistant's MQTT and templating capabilities, you unlock a new level of control and insight, turning raw, disparate data streams into a cohesive, intelligent, and highly automated smart home experience. No more letting messy data hinder your automation dreams!

Avatar picture of NGC 224
Written by:

NGC 224

Author bio: DIY Smart Home Creator

There are no comments yet
loading...