I’ve been recently trying to solve a similar problem, and I ended up coding a trigger based template sensor. It’s close to the automation idea above but you can have all the code in one place.
I’ll explain with a “living” example (part of my sensor). There are 2 MQTT topics that I want to combine into 1 sensor with attributes:
1) ebusd/bai/SetMode
{
"hcmode": {"value": "auto"},
"flowtempdesired": {"value": 0.0},
"hwctempdesired": {"value": 46.0},
"hwcflowtempdesired": {"value": null},
"disablehc": {"value": 1},
"disablehwctapping": {"value": 0},
"disablehwcload": {"value": 0},
"remoteControlHcPump": {"value": 0},
"releaseBackup": {"value": 0},
"releaseCooling": {"value": 0}
}
2) ebusd/bai/Status01
{
"0": {"name": "temp1", "value": 55.5},
"1": {"name": "temp1", "value": 53.0},
"2": {"name": "temp2", "value": 11.875},
"3": {"name": "temp1", "value": null},
"4": {"name": "temp1", "value": null},
"5": {"name": "pumpstate", "value": "off"}
}
From the 1st topic, I need the value of ‘disablehc’, which informs whether the heating mode is enabled (value 0) or disabled (1).
From the 2nd topic, I need the value of ‘pumpstate’, which can be one of: “overrun”,“on”, or “off”.
Combination of the 2 values encodes the current operation mode of my heating device, which I want to show in my sensor’s state. Individual values from both topics go to attributes. I also want to know what time last heating cycle started and ended (another 2 attributes)
Sensor code
- trigger:
- trigger: homeassistant
event: start
id: 'ha_start'
- trigger: event
event_type: event_template_reloaded
id: 'template_reload'
- trigger: mqtt
topic: ebusd/bai/SetMode
value_template: "{{ value_json }}"
id: 'setmode'
- trigger: mqtt
topic: ebusd/bai/Status01
value_template: "{{ value_json }}"
id: 'status01'
sensor:
# State meaning:
# 'unknown' - no data
# 'error' - unexpected value in MQTT data
# 'idle' - idle
# 'hwc' - hot water
# 'hc' - central heating
# 'hc_p' - central heating, paused
# 'hc_hwc' - central heating and hot water
- name: Heating Device
unique_id: heating_device_id
state: >-
{% if trigger.id == 'ha_start' %}
unknown
{% elif trigger.id == 'setmode' %}
{% set p = this.attributes.get('pump_state') %}
{% if p == 'unknown' %}
unknown
{% else %}
{% set j = trigger.payload|from_json %}
{% set hc = j['disablehc']['value'] == 0 %}
{% endif %}
{% elif trigger.id == 'status01' %}
{% set hc = this.attributes.get('hc_active') %}
{% if hc == 'unknown' %}
unknown
{% else %}
{% set j = trigger.payload|from_json %}
{% set p = j['5']['value'] %}
{% endif %}
{% else %}
{{ states(this.entity_id) }}
{% endif %}
{% if hc is defined and p is defined %}
{% if hc %}
{{ 'hc_hwc' if p == 'overrun' else 'hc' if p == 'on' else 'hc_p' if p == 'off' else 'error' }}
{% else %}
{{ 'hwc' if p == 'overrun' else 'idle' if p in ['on','off'] else 'error' }}
{% endif %}
{% endif %}
attributes:
# is heating mode active
hc_active: >-
{% if trigger.id == 'ha_start' %}
unknown
{% elif trigger.id == 'setmode' %}
{% set j = trigger.payload|from_json %}
{{ j['disablehc']['value'] == 0 }}
{% else %}
{{ this.attributes.get('hc_active') }}
{% endif %}
pump_state: >-
{% if trigger.id == 'ha_start' %}
unknown
{% elif trigger.id == 'status01' %}
{% set j = trigger.payload|from_json %}
{{ j['5']['value'] }}
{% else %}
{{ this.attributes.get('pump_state') }}
{% endif %}
# start time of last heating cycle; it will survive HA restart
hc_start: >-
{% set prev = this.attributes.get('hc_start') %}
{% if trigger.id == 'setmode' %}
{% set j = trigger.payload|from_json %}
{% set prevHcActive = this.attributes.get('hc_active') %}
{% set prevHcActive = true if prevHcActive == 'unknown' else prevHcActive %}
{{ now() if j['disablehc']['value'] == 0 and not prevHcActive else prev }}
{% else %}
{{ prev }}
{% endif %}
# end time of last heating cycle; it will survive HA restart
hc_end: >-
{% set prev = this.attributes.get('hc_end') %}
{% if trigger.id == 'setmode' %}
{% set j = trigger.payload|from_json %}
{% set prevHcActive = this.attributes.get('hc_active') %}
{% set prevHcActive = false if prevHcActive == 'unknown' else prevHcActive %}
{{ now() if j['disablehc']['value'] == 1 and prevHcActive else prev }}
{% else %}
{{ prev }}
{% endif %}
So the attributes basically act as the original MQTT sensors and are only updated when the corresponding MQTT topics have changed, otherwise - they just keep returning their previous value.
If you need to combine values from 2 MQTT topics (as in the state of my sensor), you get the new value from topic’s respective event trigger, and the value of other topic from it’s attribute (as that didn’t change).
Bonus: contrary to “normal” MQTT sensors, you can decide what should happen if templates get reloaded or Home Assistant gets restarted (that only matters, if your MQTT messages don’t have RETAIN flag set). So you can decide per-attribute; e.g. I chose to restore my sensor completely in case of template reload but render it ‘unknown’ after HA restart - except for attributes ‘hc_start’ and ‘hc_end’, which restore their values even then.