HADashboard - Second sensor in widget

@Cee,

I was using attributes from envisalink sensors. I guess last_changed on the standard sensors has changed from an attribute to a property directly off the sensor. Making the below changes to add a “state_path” to the attributes component made it work.

  - platform: attributes
    friendly_name: Test sensor
    attribute: last_changed
    time_format: '%d, %m - %H:%M:%S'
    entities:
      - input_boolean.test_boolean
# """
# Creates a sensor that breaks out attribute of defined entities.
# """
import asyncio
import logging

import voluptuous as vol

from homeassistant.core import callback
from homeassistant.components.sensor import ENTITY_ID_FORMAT, PLATFORM_SCHEMA
from homeassistant.const import (
    ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT,
    ATTR_ICON, CONF_ENTITIES, EVENT_HOMEASSISTANT_START, STATE_UNKNOWN)
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity, async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers import template as template_helper

_LOGGER = logging.getLogger(__name__)

CONF_ATTRIBUTE = "attribute"
CONF_TIME_FORMAT = "time_format"

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(ATTR_ICON): cv.string,
    vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
    vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string,
    vol.Optional(CONF_TIME_FORMAT): cv.string,
    vol.Required(CONF_ATTRIBUTE): cv.string,
    vol.Required(CONF_ENTITIES): cv.entity_ids
})


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
    """Set up the attributes sensors."""
    _LOGGER.info("Starting attribute sensor")
    sensors = []

    for device in config[CONF_ENTITIES]:
        attr = config.get(CONF_ATTRIBUTE)
        time_format = str(config.get(CONF_TIME_FORMAT))

        if (attr == "last_changed"):
            state_path = "states.{0}.{1}".format(device, attr)
        else:
            state_path = "states.{0}.attributes['{1}']".format(device, attr)        

        if (attr == "last_triggered" or
                attr == "last_changed") and time_format:

            state_template = ("{{% if states('{0}') %}}\
                              {{{{ as_timestamp({1})\
                              | int | timestamp_local()\
                              | timestamp_custom('{2}') }}}}\
                              {{% else %}} {3} {{% endif %}}").format(
                device, state_path, time_format, STATE_UNKNOWN)
        elif attr == "battery" or attr == "battery_level":
            state_template = ("{{% if states('{0}') %}}\
                              {{{{ {1} | float }}}}\
                              {{% else %}} {2} {{% endif %}}").format(
                device, state_path, STATE_UNKNOWN)
        elif attr == "last_tripped_time":
            state_template = ("{{% set time = as_timestamp(now()) - as_timestamp({1}) %}}\
                                {{% set minutes = ((time % 3600) / 60) | int %}}\
                                {{% set hours = ((time % 86400) / 3600) | int %}}\
                                {{% set days = (time / 86400) | int %}}\
                                {{%- if time < 60 -%}}\
                                    Less than a minute\
                                {{%- else -%}}\
                                    {{%- if days > 0 -%}}\
                                    {{%- if days == 1 -%}}\
                                        1 day\
                                    {{%- else -%}}\
                                        {{{{ days }}}} days\
                                    {{%- endif -%}}\
                                    {{%- endif -%}}\
                                    {{%- if hours > 0 -%}}\
                                    {{%- if days > 0 -%}}\
                                        {{{{ ', ' }}}}\
                                    {{%- endif -%}}\
                                    {{%- if hours == 1 -%}}\
                                        1 hour\
                                    {{%- else -%}}\
                                        {{{{ hours }}}} hours\
                                    {{%- endif -%}}\
                                    {{%- endif -%}}\
                                    {{%- if minutes > 0 -%}}\
                                    {{%- if days > 0 or hours > 0 -%}}\
                                        {{{{ ', ' }}}}\
                                    {{%- endif -%}}\
                                    {{%- if minutes == 1 -%}}\
                                        1 minute\
                                    {{%- else -%}}\
                                        {{{{ minutes }}}} minutes\
                                    {{%- endif -%}}\
                                    {{%- endif -%}}\
                                {{%- endif -%}}").format(
                device, state_path, STATE_UNKNOWN)
        else:
            state_template = ("{{% if states('{0}') %}}\
                              {{{{ {1} }}}}\
                              {{% else %}} {2} {{% endif %}}").format(
                device, state_path, STATE_UNKNOWN)

        _LOGGER.info("Adding attribute: %s of entity: %s", attr, device)
        _LOGGER.debug("Applying template: %s", state_template)

        state_template = template_helper.Template(state_template)
        state_template.hass = hass

        icon = str(config.get(ATTR_ICON))

        device_state = hass.states.get(device)
        if device_state is not None:
            device_friendly_name = device_state.attributes.get('friendly_name')
        else:
            device_friendly_name = None

        if device_friendly_name is None:
            device_friendly_name = device.split(".", 1)[1]

        friendly_name = config.get(ATTR_FRIENDLY_NAME, device_friendly_name)
        unit_of_measurement = config.get(ATTR_UNIT_OF_MEASUREMENT)

        if icon.startswith('mdi:'):
            _LOGGER.debug("Applying user defined icon: '%s'", icon)
            new_icon = ("{{% if states('{0}') %}} {1} {{% else %}}\
                mdi:eye {{% endif %}}").format(device, icon)

            new_icon = template_helper.Template(new_icon)
            new_icon.hass = hass
        elif attr == "battery" or attr == "battery_level":
            _LOGGER.debug("Applying battery icon template")

            new_icon = ("{{% if states('{0}') %}}\
                {{% set batt = states.{0}.attributes['{1}'] %}}\
                {{% if batt == 'unknown' %}}\
                mdi:battery-unknown\
                {{% elif batt > 95 %}}\
                mdi:battery\
                {{% elif batt > 85 %}}\
                mdi:battery-90\
                {{% elif batt > 75 %}}\
                mdi:battery-80\
                {{% elif batt > 65 %}}\
                mdi:battery-70\
                {{% elif batt > 55 %}}\
                mdi:battery-60\
                {{% elif batt > 45 %}}\
                mdi:battery-50\
                {{% elif batt > 35 %}}\
                mdi:battery-40\
                {{% elif batt > 25 %}}\
                mdi:battery-30\
                {{% elif batt > 15 %}}\
                mdi:battery-20\
                {{% elif batt > 10 %}}\
                mdi:battery-10\
                {{% else %}}\
                mdi:battery-outline\
                {{% endif %}}\
            {{% else %}}\
            mdi:battery-unknown\
            {{% endif %}}").format(device, attr)
            new_icon = template_helper.Template(str(new_icon))
            new_icon.hass = hass
        else:
            _LOGGER.debug("No icon applied")
            new_icon = None

        sensors.append(
            AttributeSensor(
                hass,
                ("{0}_{1}").format(device.split(".", 1)[1], attr),
                friendly_name,
                unit_of_measurement,
                state_template,
                new_icon,
                device)
        )
    if not sensors:
        _LOGGER.error("No sensors added")
        return False

    async_add_devices(sensors)
    return True


class AttributeSensor(Entity):
    """Representation of a Attribute Sensor."""

    def __init__(self, hass, device_id, friendly_name, unit_of_measurement,
                 state_template, icon_template, entity_id):
        """Initialize the sensor."""
        self.hass = hass
        self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id,
                                                  hass=hass)
        self._name = friendly_name
        self._unit_of_measurement = unit_of_measurement
        self._template = state_template
        self._state = None
        self._icon_template = icon_template
        self._icon = None
        self._entity = entity_id

    @asyncio.coroutine
    def async_added_to_hass(self):
        """Register callbacks."""
        state = yield from async_get_last_state(self.hass, self.entity_id)
        if state:
            self._state = state.state

        @callback
        def template_sensor_state_listener(entity, old_state, new_state):
            """Handle device state changes."""
            self.hass.async_add_job(self.async_update_ha_state(True))

        @callback
        def template_sensor_startup(event):
            """Update on startup."""
            async_track_state_change(
                self.hass, self._entity, template_sensor_state_listener)

            self.hass.async_add_job(self.async_update_ha_state(True))

        self.hass.bus.async_listen_once(
            EVENT_HOMEASSISTANT_START, template_sensor_startup)

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def state(self):
        """Return the state of the sensor."""
        return self._state

    @property
    def icon(self):
        """Return the icon to use in the frontend, if any."""
        return self._icon

    @property
    def unit_of_measurement(self):
        """Return the unit_of_measurement of the device."""
        return self._unit_of_measurement

    @property
    def should_poll(self):
        """No polling needed."""
        return False

    @asyncio.coroutine
    def async_update(self):
        """Update the state from the template and the friendly name."""

        entity_state = self.hass.states.get(self._entity)
        if entity_state is not None:
            device_friendly_name = entity_state.attributes.get('friendly_name')
        else:
            device_friendly_name = None

        if device_friendly_name is not None:
            self._name = device_friendly_name

        try:
            self._state = self._template.async_render()
        except TemplateError as ex:
            if ex.args and ex.args[0].startswith(
                    "UndefinedError: 'None' has no attribute"):
                # Common during HA startup - so just a warning
                _LOGGER.warning('Could not render attribute sensor for %s,'
                                ' the state is unknown.', self._entity)
                return
            self._state = None
            _LOGGER.error('Could not attribute sensor for %s: %s',
                          self._entity, ex)

        if self._icon_template is not None:
            try:
                self._icon = self._icon_template.async_render()
            except TemplateError as ex:
                if ex.args and ex.args[0].startswith(
                        "UndefinedError: 'None' has no attribute"):
                    # Common during HA startup - so just a warning
                    _LOGGER.warning('Could not render icon template %s,'
                                    ' the state is unknown.', self._name)
                    return
                self._icon = super().icon
                _LOGGER.error('Could not render icon template %s: %s',
                              self._name, ex)
1 Like

For some reason, template sensors using last_changed as relative time don’t update, while my template sensors using the last_tripped_time from my Envisalink sensor as relative time do. Anyone know why that would be? Is there a way to “trick” the template sensor to update on a regular basis? Absolute timestamps work for both types of sensors.

Thank you again for the work you are putting into this @kodbuse , super appreciate it.

I am using 3 Xiaomi door/window sensors, 1 of their motion sensors and 2 hue motion sensors.

I think the Xiaomi doesn’t do much else apart from open/close/battery level, they dont have many attributes in the states page.

Where as the Hue ones have a bit more information with them,

I changed the attributes component to the code above you pasted, and it seemed to sort of work, I now get the sensors populated, but with a normal date and time.

attributes%20sensors%201

attributes%20sensors%202

I apologise as I am not good at coding at all, but I from what I think I could work out, it should be the last_tripped_time ? attribute ? but when I change attributes sensor I get this

Could not attribute sensor for sensor.hallway_motion_motion_sensor: UndefinedError: 'mappingproxy object' has no attribute 'last_tripped_time'

and then nothing works again.

Kinda getting there, but also notice you were having problems with this way as well. So not sure if anything here helps you out at all.

If there is anything I can do to help out solve, just please let me know.

Many thanks again.

@Cee,

Based on what I’ve seen, with the Xiaomi sensors, you’ll probably not get them to update using last_changed with relative times. However, with the Hue sensor you might have better luck, since it seems to have a separate last_updated attribute.

Right, the last_tripped_time attribute is specific to Envisalink. Sorry, I thought you wanted the absolute time since you had included a time_format in one of the posts above. It’s not my code originally, but I have modified it so that you can use any of the timestamp attributes discussed with relative or absolute time (see below). At some point, I’ll probably modify it take some sort of parameter to indicate how to treat the value, instead of hardcoding the attribute names. Anyway, now the presence (or lack of) time_format will control whether the time is absolute or relative.

When using last_updated with the Hue sensor, I wonder if the comma in the value will break the parsing. I tried it in the template editor and it seemed replacing it with a comma was necessary to fix it:

{{ as_timestamp("2018-09-09,08:51:48".replace(",", " ")) }}

You might need to make the corresponding edit in the attributes component where as_timestamp is used.

Relative example:

  - platform: attributes
    friendly_name: Last Triggered Envisalink
    attribute: last_tripped_time
    entities:
      - binary_sensor.front_door
      - binary_sensor.back_door

Absolute example:

  - platform: attributes
    friendly_name: Porch motion last updated
    attribute: last_changed
    time_format: "%m/%d %I:%M%p"
    entities:
      - sensor.aeotec_zw100_multisensor_6_burglar

New component code:

# """
# Creates a sensor that breaks out attribute of defined entities.
# """
import asyncio
import logging

import voluptuous as vol

from homeassistant.core import callback
from homeassistant.components.sensor import ENTITY_ID_FORMAT, PLATFORM_SCHEMA
from homeassistant.const import (
    ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT,
    ATTR_ICON, CONF_ENTITIES, EVENT_HOMEASSISTANT_START, STATE_UNKNOWN)
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity, async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers import template as template_helper

_LOGGER = logging.getLogger(__name__)

CONF_ATTRIBUTE = "attribute"
CONF_TIME_FORMAT = "time_format"

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(ATTR_ICON): cv.string,
    vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
    vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string,
    vol.Optional(CONF_TIME_FORMAT): cv.string,
    vol.Required(CONF_ATTRIBUTE): cv.string,
    vol.Required(CONF_ENTITIES): cv.entity_ids
})


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
    """Set up the attributes sensors."""
    _LOGGER.info("Starting attribute sensor")
    sensors = []

    for device in config[CONF_ENTITIES]:
        attr = config.get(CONF_ATTRIBUTE)
        time_format = str(config.get(CONF_TIME_FORMAT))

        if (attr == "last_changed"):
            state_path = "states.{0}.{1}".format(device, attr)
        else:
            state_path = "states.{0}.attributes['{1}']".format(device, attr)        

        _LOGGER.info("time_format: {0}".format(time_format))
        _LOGGER.info("time_format type: {0}".format(type(time_format)))
        if ((attr == "last_tripped_time" or attr == "last_changed" or attr == "last_triggered" or attr == "last_updated") and time_format != None and time_format != "None"):
            state_template = ("{{% if states('{0}') %}}\
                              {{{{ as_timestamp({1})\
                              | int | timestamp_custom('{2}') }}}}\
                              {{% else %}} {3} {{% endif %}}").format(
                device, state_path, time_format, STATE_UNKNOWN)
        elif attr == "battery" or attr == "battery_level":
            state_template = ("{{% if states('{0}') %}}\
                              {{{{ {1} | float }}}}\
                              {{% else %}} {2} {{% endif %}}").format(
                device, state_path, STATE_UNKNOWN)
        elif attr == "last_tripped_time" or attr == "last_changed" or attr == "last_triggered" or attr == "last_updated":
            state_template = ("{{% set time = as_timestamp({1}) | int %}}\
                                {{% set diff = (as_timestamp(now()) - time) | int %}}\
                                {{% set minutes = ((diff % 3600) / 60) | int %}}\
                                {{% set hours = ((diff % 86400) / 3600) | int %}}\
                                {{% set days = (diff / 86400) | int %}}\
                                {{%- if not states('{0}') or not time -%}}\
                                    Unknown\
                                {{%- elif diff < 60 -%}}\
                                    Less than a minute\
                                {{%- else -%}}\
                                    {{%- if days > 0 -%}}\
                                        {{%- if days == 1 -%}}\
                                            1 day\
                                        {{%- else -%}}\
                                            {{{{ days }}}} days\
                                        {{%- endif -%}}\
                                    {{%- endif -%}}\
                                    {{%- if hours > 0 -%}}\
                                        {{%- if days > 0 -%}}\
                                            {{{{ ', ' }}}}\
                                        {{%- endif -%}}\
                                        {{%- if hours == 1 -%}}\
                                            1 hour\
                                        {{%- else -%}}\
                                            {{{{ hours }}}} hours\
                                        {{%- endif -%}}\
                                    {{%- endif -%}}\
                                    {{%- if minutes > 0 -%}}\
                                        {{%- if days > 0 or hours > 0 -%}}\
                                            {{{{ ', ' }}}}\
                                        {{%- endif -%}}\
                                        {{%- if minutes == 1 -%}}\
                                            1 minute\
                                        {{%- else -%}}\
                                            {{{{ minutes }}}} minutes\
                                        {{%- endif -%}}\
                                    {{%- endif -%}}\
                                {{%- endif -%}}").format(
                device, state_path, STATE_UNKNOWN)
        else:
            state_template = ("{{% if states('{0}') %}}\
                              {{{{ {1} }}}}\
                              {{% else %}} {2} {{% endif %}}").format(
                device, state_path, STATE_UNKNOWN)

        _LOGGER.info("Adding attribute: %s of entity: %s", attr, device)
        _LOGGER.debug("Applying template: %s", state_template)

        state_template = template_helper.Template(state_template)
        state_template.hass = hass

        icon = str(config.get(ATTR_ICON))

        device_state = hass.states.get(device)
        if device_state is not None:
            device_friendly_name = device_state.attributes.get('friendly_name')
        else:
            device_friendly_name = None

        if device_friendly_name is None:
            device_friendly_name = device.split(".", 1)[1]

        friendly_name = config.get(ATTR_FRIENDLY_NAME, device_friendly_name)
        unit_of_measurement = config.get(ATTR_UNIT_OF_MEASUREMENT)

        if icon.startswith('mdi:'):
            _LOGGER.debug("Applying user defined icon: '%s'", icon)
            new_icon = ("{{% if states('{0}') %}} {1} {{% else %}}\
                mdi:eye {{% endif %}}").format(device, icon)

            new_icon = template_helper.Template(new_icon)
            new_icon.hass = hass
        elif attr == "battery" or attr == "battery_level":
            _LOGGER.debug("Applying battery icon template")

            new_icon = ("{{% if states('{0}') %}}\
                {{% set batt = states.{0}.attributes['{1}'] %}}\
                {{% if batt == 'unknown' %}}\
                mdi:battery-unknown\
                {{% elif batt > 95 %}}\
                mdi:battery\
                {{% elif batt > 85 %}}\
                mdi:battery-90\
                {{% elif batt > 75 %}}\
                mdi:battery-80\
                {{% elif batt > 65 %}}\
                mdi:battery-70\
                {{% elif batt > 55 %}}\
                mdi:battery-60\
                {{% elif batt > 45 %}}\
                mdi:battery-50\
                {{% elif batt > 35 %}}\
                mdi:battery-40\
                {{% elif batt > 25 %}}\
                mdi:battery-30\
                {{% elif batt > 15 %}}\
                mdi:battery-20\
                {{% elif batt > 10 %}}\
                mdi:battery-10\
                {{% else %}}\
                mdi:battery-outline\
                {{% endif %}}\
            {{% else %}}\
            mdi:battery-unknown\
            {{% endif %}}").format(device, attr)
            new_icon = template_helper.Template(str(new_icon))
            new_icon.hass = hass
        else:
            _LOGGER.debug("No icon applied")
            new_icon = None

        sensors.append(
            AttributeSensor(
                hass,
                ("{0}_{1}").format(device.split(".", 1)[1], attr),
                friendly_name,
                unit_of_measurement,
                state_template,
                new_icon,
                device)
        )
    if not sensors:
        _LOGGER.error("No sensors added")
        return False

    async_add_devices(sensors)
    return True


class AttributeSensor(Entity):
    """Representation of a Attribute Sensor."""

    def __init__(self, hass, device_id, friendly_name, unit_of_measurement,
                 state_template, icon_template, entity_id):
        """Initialize the sensor."""
        self.hass = hass
        self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id,
                                                  hass=hass)
        self._name = friendly_name
        self._unit_of_measurement = unit_of_measurement
        self._template = state_template
        self._state = None
        self._icon_template = icon_template
        self._icon = None
        self._entity = entity_id

    @asyncio.coroutine
    def async_added_to_hass(self):
        """Register callbacks."""
        state = yield from async_get_last_state(self.hass, self.entity_id)
        if state:
            self._state = state.state

        @callback
        def template_sensor_state_listener(entity, old_state, new_state):
            """Handle device state changes."""
            self.hass.async_add_job(self.async_update_ha_state(True))

        @callback
        def template_sensor_startup(event):
            """Update on startup."""
            async_track_state_change(
                self.hass, self._entity, template_sensor_state_listener)

            self.hass.async_add_job(self.async_update_ha_state(True))

        self.hass.bus.async_listen_once(
            EVENT_HOMEASSISTANT_START, template_sensor_startup)

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def state(self):
        """Return the state of the sensor."""
        return self._state

    @property
    def icon(self):
        """Return the icon to use in the frontend, if any."""
        return self._icon

    @property
    def unit_of_measurement(self):
        """Return the unit_of_measurement of the device."""
        return self._unit_of_measurement

    @property
    def should_poll(self):
        """No polling needed."""
        return False

    @asyncio.coroutine
    def async_update(self):
        """Update the state from the template and the friendly name."""

        entity_state = self.hass.states.get(self._entity)
        if entity_state is not None:
            device_friendly_name = entity_state.attributes.get('friendly_name')
        else:
            device_friendly_name = None

        if device_friendly_name is not None:
            self._name = device_friendly_name

        try:
            self._state = self._template.async_render()
        except TemplateError as ex:
            if ex.args and ex.args[0].startswith(
                    "UndefinedError: 'None' has no attribute"):
                # Common during HA startup - so just a warning
                _LOGGER.warning('Could not render attribute sensor for %s,'
                                ' the state is unknown.', self._entity)
                return
            self._state = None
            _LOGGER.error('Could not attribute sensor for %s: %s',
                          self._entity, ex)

        if self._icon_template is not None:
            try:
                self._icon = self._icon_template.async_render()
            except TemplateError as ex:
                if ex.args and ex.args[0].startswith(
                        "UndefinedError: 'None' has no attribute"):
                    # Common during HA startup - so just a warning
                    _LOGGER.warning('Could not render icon template %s,'
                                    ' the state is unknown.', self._name)
                    return
                self._icon = super().icon
                _LOGGER.error('Could not render icon template %s: %s',
                              self._name, ex)
1 Like

Thank you very much again @kodbuse your a superstar.

I had been trying to get something working for a while, so I think my post’s might have been a bit confusing. I am looking to get it setup exactly as you have it, your screenshot looks just like the photoshop I did up a while back as an example

with the sensor in the tile, and then the time since it was triggered underneath. I know the Xiaomi are cheap sensors, I had Hive one before, but they had a stupid two minute polling time, so were pointless for HA.

So would it be possible to maybe setup an input_boolean that would mirror the state of the door sensors, and then maybe have the attributes code run off that ? Looking at the attributes, I am going to guess probably not, just trying to think of a creative way around it.

@Cee,

This may be a bug, or perhaps it was intentionally or accidentally disabled because it was causing some sort of infinite update recursion or performance problem. I started another thread specifically about this. Hopefully we’ll get some answers:

1 Like

Awesome, thank you very much again @kodbuse for the effort you are putting into this :slight_smile: I wish I could be more helpful, but I get lost in code :frowning:

Hey!

Any chance you can share working code/instruction, tried to work with your widget from github wihout luck :
Looking how i can get motion/door since last state (i’m using xiaomi sensors) like this:
image

Thanks!

@Cee,

I got it working by explicitly tracking sensor.time in the generated attribute sensors. Here’s the new code:

# """
# Creates a sensor that breaks out attribute of defined entities.
# """
import asyncio
import logging

import voluptuous as vol

from homeassistant.core import callback
from homeassistant.components.sensor import ENTITY_ID_FORMAT, PLATFORM_SCHEMA
from homeassistant.const import (
    ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT,
    ATTR_ICON, CONF_ENTITIES, EVENT_HOMEASSISTANT_START, STATE_UNKNOWN)
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity, async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers import template as template_helper

_LOGGER = logging.getLogger(__name__)

CONF_ATTRIBUTE = "attribute"
CONF_TIME_FORMAT = "time_format"

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(ATTR_ICON): cv.string,
    vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
    vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string,
    vol.Optional(CONF_TIME_FORMAT): cv.string,
    vol.Required(CONF_ATTRIBUTE): cv.string,
    vol.Required(CONF_ENTITIES): cv.entity_ids
})


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
    """Set up the attributes sensors."""
    _LOGGER.info("Starting attribute sensor")
    sensors = []

    for device in config[CONF_ENTITIES]:
        attr = config.get(CONF_ATTRIBUTE)
        time_format = str(config.get(CONF_TIME_FORMAT))

        if (attr == "last_changed"):
            state_path = "states.{0}['{1}']".format(device, attr)
        else:
            state_path = "states.{0}.attributes['{1}']".format(device, attr)        

        _LOGGER.info("time_format: {0}".format(time_format))
        _LOGGER.info("time_format type: {0}".format(type(time_format)))
        if ((attr == "last_tripped_time" or attr == "last_changed" or attr == "last_triggered" or attr == "last_updated") and time_format != None and time_format != "None"):
            state_template = ("{{% if states('{0}') %}}\
                              {{{{ as_timestamp({1})\
                              | int | timestamp_custom('{2}') }}}}\
                              {{% else %}} {3} {{% endif %}}").format(
                device, state_path, time_format, STATE_UNKNOWN)
        elif attr == "battery" or attr == "battery_level":
            state_template = ("{{% if states('{0}') %}}\
                              {{{{ {1} | float }}}}\
                              {{% else %}} {2} {{% endif %}}").format(
                device, state_path, STATE_UNKNOWN)
        elif attr == "last_tripped_time" or attr == "last_changed" or attr == "last_triggered" or attr == "last_updated":
            state_template = ("{{% set time = as_timestamp({1}) | int %}}\
                                {{% set diff = (as_timestamp(now()) - time) | int %}}\
                                {{% set minutes = ((diff % 3600) / 60) | int %}}\
                                {{% set hours = ((diff % 86400) / 3600) | int %}}\
                                {{% set days = (diff / 86400) | int %}}\
                                {{%- if not states('{0}') or not time -%}}\
                                    Unknown\
                                {{%- elif diff < 60 -%}}\
                                    {{{{ diff }}}} seconds\
                                {{%- else -%}}\
                                    {{%- if days > 0 -%}}\
                                        {{%- if days == 1 -%}}\
                                            1 day\
                                        {{%- else -%}}\
                                            {{{{ days }}}} days\
                                        {{%- endif -%}}\
                                    {{%- endif -%}}\
                                    {{%- if hours > 0 -%}}\
                                        {{%- if days > 0 -%}}\
                                            {{{{ ', ' }}}}\
                                        {{%- endif -%}}\
                                        {{%- if hours == 1 -%}}\
                                            1 hour\
                                        {{%- else -%}}\
                                            {{{{ hours }}}} hours\
                                        {{%- endif -%}}\
                                    {{%- endif -%}}\
                                    {{%- if minutes > 0 -%}}\
                                        {{%- if days > 0 or hours > 0 -%}}\
                                            {{{{ ', ' }}}}\
                                        {{%- endif -%}}\
                                        {{%- if minutes == 1 -%}}\
                                            1 minute\
                                        {{%- else -%}}\
                                            {{{{ minutes }}}} minutes\
                                        {{%- endif -%}}\
                                    {{%- endif -%}}\
                                {{%- endif -%}}").format(
                device, state_path, STATE_UNKNOWN)
        else:
            state_template = ("{{% if states('{0}') %}}\
                              {{{{ {1} }}}}\
                              {{% else %}} {2} {{% endif %}}").format(
                device, state_path, STATE_UNKNOWN)

        _LOGGER.info("Adding attribute: %s of entity: %s", attr, device)
        _LOGGER.debug("Applying template: %s", state_template)

        state_template = template_helper.Template(state_template)
        state_template.hass = hass

        icon = str(config.get(ATTR_ICON))

        device_state = hass.states.get(device)
        if device_state is not None:
            device_friendly_name = device_state.attributes.get('friendly_name')
        else:
            device_friendly_name = None

        if device_friendly_name is None:
            device_friendly_name = device.split(".", 1)[1]

        friendly_name = config.get(ATTR_FRIENDLY_NAME, device_friendly_name)
        unit_of_measurement = config.get(ATTR_UNIT_OF_MEASUREMENT)

        if icon.startswith('mdi:'):
            _LOGGER.debug("Applying user defined icon: '%s'", icon)
            new_icon = ("{{% if states('{0}') %}} {1} {{% else %}}\
                mdi:eye {{% endif %}}").format(device, icon)

            new_icon = template_helper.Template(new_icon)
            new_icon.hass = hass
        elif attr == "battery" or attr == "battery_level":
            _LOGGER.debug("Applying battery icon template")

            new_icon = ("{{% if states('{0}') %}}\
                {{% set batt = states.{0}.attributes['{1}'] %}}\
                {{% if batt == 'unknown' %}}\
                mdi:battery-unknown\
                {{% elif batt > 95 %}}\
                mdi:battery\
                {{% elif batt > 85 %}}\
                mdi:battery-90\
                {{% elif batt > 75 %}}\
                mdi:battery-80\
                {{% elif batt > 65 %}}\
                mdi:battery-70\
                {{% elif batt > 55 %}}\
                mdi:battery-60\
                {{% elif batt > 45 %}}\
                mdi:battery-50\
                {{% elif batt > 35 %}}\
                mdi:battery-40\
                {{% elif batt > 25 %}}\
                mdi:battery-30\
                {{% elif batt > 15 %}}\
                mdi:battery-20\
                {{% elif batt > 10 %}}\
                mdi:battery-10\
                {{% else %}}\
                mdi:battery-outline\
                {{% endif %}}\
            {{% else %}}\
            mdi:battery-unknown\
            {{% endif %}}").format(device, attr)
            new_icon = template_helper.Template(str(new_icon))
            new_icon.hass = hass
        else:
            _LOGGER.debug("No icon applied")
            new_icon = None

        sensors.append(
            AttributeSensor(
                hass,
                ("{0}_{1}").format(device.split(".", 1)[1], attr),
                friendly_name,
                unit_of_measurement,
                state_template,
                new_icon,
                device)
        )
    if not sensors:
        _LOGGER.error("No sensors added")
        return False

    async_add_devices(sensors)
    return True


class AttributeSensor(Entity):
    """Representation of a Attribute Sensor."""

    def __init__(self, hass, device_id, friendly_name, unit_of_measurement,
                 state_template, icon_template, entity_id):
        """Initialize the sensor."""
        self.hass = hass
        self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id,
                                                  hass=hass)
        self._name = friendly_name
        self._unit_of_measurement = unit_of_measurement
        self._template = state_template
        self._state = None
        self._icon_template = icon_template
        self._icon = None
        self._entity = entity_id

    @asyncio.coroutine
    def async_added_to_hass(self):
        """Register callbacks."""
        state = yield from async_get_last_state(self.hass, self.entity_id)
        if state:
            self._state = state.state

        @callback
        def template_sensor_state_listener(entity, old_state, new_state):
            """Handle device state changes."""
            self.hass.async_add_job(self.async_update_ha_state(True))

        @callback
        def template_sensor_startup(event):
            """Update on startup."""
            _LOGGER.info('Tracking state of %s', self._entity);
            async_track_state_change(
                self.hass, [self._entity, 'sensor.time'], template_sensor_state_listener)

            self.hass.async_add_job(self.async_update_ha_state(True))

        self.hass.bus.async_listen_once(
            EVENT_HOMEASSISTANT_START, template_sensor_startup)

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def state(self):
        """Return the state of the sensor."""
        return self._state

    @property
    def icon(self):
        """Return the icon to use in the frontend, if any."""
        return self._icon

    @property
    def unit_of_measurement(self):
        """Return the unit_of_measurement of the device."""
        return self._unit_of_measurement

    @property
    def should_poll(self):
        """No polling needed."""
        return False

    @asyncio.coroutine
    def async_update(self):
        """Update the state from the template and the friendly name."""

        _LOGGER.info('Updating state for %s', self._name)

        entity_state = self.hass.states.get(self._entity)
        if entity_state is not None:
            device_friendly_name = entity_state.attributes.get('friendly_name')
        else:
            device_friendly_name = None

        if device_friendly_name is not None:
            self._name = device_friendly_name

        try:
            self._state = self._template.async_render()
        except TemplateError as ex:
            if ex.args and ex.args[0].startswith(
                    "UndefinedError: 'None' has no attribute"):
                # Common during HA startup - so just a warning
                _LOGGER.warning('Could not render attribute sensor for %s,'
                                ' the state is unknown.', self._entity)
                return
            self._state = None
            _LOGGER.error('Could not attribute sensor for %s: %s',
                          self._entity, ex)

        if self._icon_template is not None:
            try:
                self._icon = self._icon_template.async_render()
            except TemplateError as ex:
                if ex.args and ex.args[0].startswith(
                        "UndefinedError: 'None' has no attribute"):
                    # Common during HA startup - so just a warning
                    _LOGGER.warning('Could not render icon template %s,'
                                    ' the state is unknown.', self._name)
                    return
                self._icon = super().icon
                _LOGGER.error('Could not render icon template %s: %s',
                              self._name, ex)
1 Like

@radinsky,

My HA configuration has attribute sensors like this:

sensor:
  - platform: attributes
    friendly_name: Porch motion last updated
    attribute: last_changed
    entities:
      - sensor.aeotec_zw100_multisensor_6_burglar

And my dashboard has widgets like this:

porch:
    widget_type: sensorex
    entity: sensor.aeotec_zw100_multisensor_6_burglar
    icon_on: mdi-human
    icon_off: mdi-human
    title: Porch
    sub_entity: sensor.aeotec_zw100_multisensor_6_burglar_last_changed

Does that help?

Just had a quick play around with it, and seem’s to be working fine on the Xiaomi motion sensor, and the 2 Hue motion sensors I have, but it doesn’t seem to like the Xiaomi door sensor’s I have, it just stays at the same time.

I am a bit short of time this weekend, so probably wont have a chance to play around more till tomorrow evening.

dasah

Hey @kodbuse

Still have problems with Xioami door sensors. The motion sensor is fine, that works perfectly with the script, but I just get 0 updates from the door sensors. Dont suppose you might know why this is, or anything I can do to help out debug why they might not work ?

The motion sensor that works is at the bottom, the door sensor that doesn’t is at the top.

Many thanks in advance for any help.

@Cee

I’ve been away on vacation. So to clarify, are the values not updating, or are you not getting any valid values in the first place? I’m a bit confused since your screenshot (which looks nice, BTW), says “Closed” rather than a time value for the doors. What attribute are you using for those sensors, and what value are you seeing for those attributes if you look at them in the template editor, with something like
{{ states.binary_sensor.door_window_sensor_158d00027b5a03.last_changed }}
or
{{ states.binary_sensor.door_window_sensor_158d00027b5a03.attributes.some_other_attribute }}

1 Like

Hope you had an awesome vacation @kodbuse.

Sorry I should have added in more information. They are the actual sensors I am trying to get the last changed attributes off. Just thought it might help as one of them worked and one didn’t for the custom component.

This is the living room Xioami sensor that does work,

and these are the Xioami door sensors that don’t work,

As you can see though the times don’t match up to what is actually happened,

door%20HA

The last changed attribute doesn’t ever change from a few seconds.

middle%20door%20HA

{{ states.binary_sensor.door_window_sensor_158d00027b5a03.attributes }}

returns

{'custom_ui_state_card': 'state-card-custom-ui', 'battery_level': 37.0, 'Open since': 0, 'device_class': 'opening', 'friendly_name': 'Middle Door Sensor', 'show_last_changed': True}

and {{ states.binary_sensor.door_window_sensor_158d00027b5a03.last_changed }}

returns

2018-10-12 12:51:56.225087+00:00

Which is correct, apart from it seem’s to run an hour out, but that’s another problem.

So technically, from what I can see, it should work, and as above, does fine on the Hue sensors, and Xioami Motion sensor, just not on the Xioami door sensors.

Any help is super appreciated.

@Cee,

I agree, from what I can see, it should work with what you have there. Have you checked the log file for any errors? I usually watch the log in real-time using “tail -f home-assistant.log”. You can also enable some more logging for the component:

logger:
  default: warn
  logs:
    custom_components.sensor.attributes: debug
1 Like

Many many thanks for the help on this @kodbuse

So this is what I get in the web log with the debug set on.

2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] Starting attribute sensor
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] time_format: None
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] time_format type: <class 'str'>
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] Adding attribute: last_changed of entity: sensor.downstairs_motion_motion_sensor
2018-10-13 09:48:38 DEBUG (MainThread) [custom_components.sensor.attributes] Applying template: {% set time = as_timestamp(states.sensor.downstairs_motion_motion_sensor['last_changed']) | int %}                                {% set diff = (as_timestamp(now()) - time) | int %}                                {% set minutes = ((diff % 3600) / 60) | int %}                                {% set hours = ((diff % 86400) / 3600) | int %}                                {% set days = (diff / 86400) | int %}                                {%- if not states('sensor.downstairs_motion_motion_sensor') or not time -%}                                    Unknown                                {%- elif diff < 60 -%}                                    {{ diff }} seconds                                {%- else -%}                                    {%- if days > 0 -%}                                        {%- if days == 1 -%}                                            1 day                                        {%- else -%}                                            {{ days }} days                                        {%- endif -%}                                    {%- endif -%}                                    {%- if hours > 0 -%}                                        {%- if days > 0 -%}                                            {{ ', ' }}                                        {%- endif -%}                                        {%- if hours == 1 -%}                                            1 hour                                        {%- else -%}                                            {{ hours }} hours                                        {%- endif -%}                                    {%- endif -%}                                    {%- if minutes > 0 -%}                                        {%- if days > 0 or hours > 0 -%}                                            {{ ', ' }}                                        {%- endif -%}                                        {%- if minutes == 1 -%}                                            1 minute                                        {%- else -%}                                            {{ minutes }} minutes                                        {%- endif -%}                                    {%- endif -%}                                {%- endif -%}
2018-10-13 09:48:38 DEBUG (MainThread) [custom_components.sensor.attributes] No icon applied
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] time_format: None
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] time_format type: <class 'str'>
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] Adding attribute: last_changed of entity: sensor.hallway_motion_motion_sensor
2018-10-13 09:48:38 DEBUG (MainThread) [custom_components.sensor.attributes] Applying template: {% set time = as_timestamp(states.sensor.hallway_motion_motion_sensor['last_changed']) | int %}                                {% set diff = (as_timestamp(now()) - time) | int %}                                {% set minutes = ((diff % 3600) / 60) | int %}                                {% set hours = ((diff % 86400) / 3600) | int %}                                {% set days = (diff / 86400) | int %}                                {%- if not states('sensor.hallway_motion_motion_sensor') or not time -%}                                    Unknown                                {%- elif diff < 60 -%}                                    {{ diff }} seconds                                {%- else -%}                                    {%- if days > 0 -%}                                        {%- if days == 1 -%}                                            1 day                                        {%- else -%}                                            {{ days }} days                                        {%- endif -%}                                    {%- endif -%}                                    {%- if hours > 0 -%}                                        {%- if days > 0 -%}                                            {{ ', ' }}                                        {%- endif -%}                                        {%- if hours == 1 -%}                                            1 hour                                        {%- else -%}                                            {{ hours }} hours                                        {%- endif -%}                                    {%- endif -%}                                    {%- if minutes > 0 -%}                                        {%- if days > 0 or hours > 0 -%}                                            {{ ', ' }}                                        {%- endif -%}                                        {%- if minutes == 1 -%}                                            1 minute                                        {%- else -%}                                            {{ minutes }} minutes                                        {%- endif -%}                                    {%- endif -%}                                {%- endif -%}
2018-10-13 09:48:38 DEBUG (MainThread) [custom_components.sensor.attributes] No icon applied
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] time_format: None
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] time_format type: <class 'str'>
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] Adding attribute: last_changed of entity: binary_sensor.motion_sensor_158d0002281e2f
2018-10-13 09:48:38 DEBUG (MainThread) [custom_components.sensor.attributes] Applying template: {% set time = as_timestamp(states.binary_sensor.motion_sensor_158d0002281e2f['last_changed']) | int %}                                {% set diff = (as_timestamp(now()) - time) | int %}                                {% set minutes = ((diff % 3600) / 60) | int %}                                {% set hours = ((diff % 86400) / 3600) | int %}                                {% set days = (diff / 86400) | int %}                                {%- if not states('binary_sensor.motion_sensor_158d0002281e2f') or not time -%}                                    Unknown                                {%- elif diff < 60 -%}                                    {{ diff }} seconds                                {%- else -%}                                    {%- if days > 0 -%}                                        {%- if days == 1 -%}                                            1 day                                        {%- else -%}                                            {{ days }} days                                        {%- endif -%}                                    {%- endif -%}                                    {%- if hours > 0 -%}                                        {%- if days > 0 -%}                                            {{ ', ' }}                                        {%- endif -%}                                        {%- if hours == 1 -%}                                            1 hour                                        {%- else -%}                                            {{ hours }} hours                                        {%- endif -%}                                    {%- endif -%}                                    {%- if minutes > 0 -%}                                        {%- if days > 0 or hours > 0 -%}                                            {{ ', ' }}                                        {%- endif -%}                                        {%- if minutes == 1 -%}                                            1 minute                                        {%- else -%}                                            {{ minutes }} minutes                                        {%- endif -%}                                    {%- endif -%}                                {%- endif -%}
2018-10-13 09:48:38 DEBUG (MainThread) [custom_components.sensor.attributes] No icon applied
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] Starting attribute sensor
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] time_format: None
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] time_format type: <class 'str'>
2018-10-13 09:48:38 INFO (MainThread) [custom_components.sensor.attributes] Adding attribute: last_changed of entity: binary_sensor.door_window_sensor_158d00027b5a03
2018-10-13 09:48:38 DEBUG (MainThread) [custom_components.sensor.attributes] Applying template: {% set time = as_timestamp(states.binary_sensor.door_window_sensor_158d00027b5a03['last_changed']) | int %}                                {% set diff = (as_timestamp(now()) - time) | int %}                                {% set minutes = ((diff % 3600) / 60) | int %}                                {% set hours = ((diff % 86400) / 3600) | int %}                                {% set days = (diff / 86400) | int %}                                {%- if not states('binary_sensor.door_window_sensor_158d00027b5a03') or not time -%}                                    Unknown                                {%- elif diff < 60 -%}                                    {{ diff }} seconds                                {%- else -%}                                    {%- if days > 0 -%}                                        {%- if days == 1 -%}                                            1 day                                        {%- else -%}                                            {{ days }} days                                        {%- endif -%}                                    {%- endif -%}                                    {%- if hours > 0 -%}                                        {%- if days > 0 -%}                                            {{ ', ' }}                                        {%- endif -%}                                        {%- if hours == 1 -%}                                            1 hour                                        {%- else -%}                                            {{ hours }} hours                                        {%- endif -%}                                    {%- endif -%}                                    {%- if minutes > 0 -%}                                        {%- if days > 0 or hours > 0 -%}                                            {{ ', ' }}                                        {%- endif -%}                                        {%- if minutes == 1 -%}                                            1 minute                                        {%- else -%}                                            {{ minutes }} minutes                                        {%- endif -%}                                    {%- endif -%}                                {%- endif -%}
2018-10-13 09:48:38 DEBUG (MainThread) [custom_components.sensor.attributes] No icon applied
2018-10-13 09:48:55 INFO (MainThread) [custom_components.sensor.attributes] Tracking state of sensor.downstairs_motion_motion_sensor
2018-10-13 09:48:55 INFO (MainThread) [custom_components.sensor.attributes] Tracking state of binary_sensor.motion_sensor_158d0002281e2f
2018-10-13 09:48:55 INFO (MainThread) [custom_components.sensor.attributes] Tracking state of sensor.hallway_motion_motion_sensor
2018-10-13 09:48:55 INFO (MainThread) [custom_components.sensor.attributes] Tracking state of binary_sensor.door_window_sensor_158d00027b5a03
2018-10-13 09:48:56 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Motion Sensors
2018-10-13 09:48:56 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Motion Sensors
2018-10-13 09:48:56 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Motion Sensors
2018-10-13 09:48:56 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Door Sensors
2018-10-13 09:49:22 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Hallway Motion

When i view the .home-assistant.log I get this,

2018-10-13 09:37:31 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Living Room Motion
2018-10-13 09:37:42 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Downstairs Motion
2018-10-13 09:37:47 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Downstairs Motion
2018-10-13 09:37:50 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Middle Door Sensor
2018-10-13 09:37:53 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Middle Door Sensor
2018-10-13 09:38:09 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Downstairs Motion
2018-10-13 09:39:23 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Hallway Motion
2018-10-13 09:39:31 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Living Room Motion
2018-10-13 09:40:31 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Living Room Motion
2018-10-13 09:42:30 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Living Room Motion
2018-10-13 09:42:46 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Downstairs Motion
2018-10-13 09:43:08 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Downstairs Motion
2018-10-13 09:47:30 INFO (MainThread) [custom_components.sensor.attributes] Updating state for Living Room Motion

I had walked downstairs to open the middle door to see what happens, which trips the living room and downstairs motion sensors. Can see them update periodically, but the door sensor only seems to update when I its either opened or closed, doesn’t seem to do the time’d updates.

This is my sensor code, not sure if I have messed something up. I had them all under one heading, but then split them into two just to see if that made a difference.

- platform: attributes
  friendly_name: Motion Sensors
  attribute: last_changed
  entities:
    - sensor.downstairs_motion_motion_sensor
    - sensor.hallway_motion_motion_sensor
    - binary_sensor.motion_sensor_158d0002281e2f

- platform: attributes
  friendly_name: Door Sensors
  attribute: last_changed
  entities:
    - binary_sensor.door_window_sensor_158d00027b5a03

I just have the middle door sensor setup for now to make it easier to try and debug.

@Cee,

I’m guessing it’s because you don’t have the time platform enabled. Try adding it under sensors like so:

sensor:
  - platform: time_date
    display_options:
      - 'time'
1 Like

Thank you, thank you so much @kodbuse that solved it :smiley: Time to adjust everything and get it setup in HADashboard :+1:

There we go, that was something I had been after for ages, and had asked @ReneTode about it, but it was beyond my skill level. Thank you again for helping me get this setup @kodbuse

1 Like

@Cee, you’re very welcome! I’m glad it’s working! Well done, that looks great!

1 Like