Mqtt rework on value templates (#62105)

* add MqttValueTemplate class

* support variables at initiation

* pass MqttEntity instead of hass

* Use MqttValueTemplace class for value templates

* make hass en enitity parameters conditional

* remove unused property and remove None assignment

* rename self._attr_value_template
This commit is contained in:
Jan Bouwhuis 2022-01-03 16:07:40 +01:00 committed by GitHub
parent bf78ddcadb
commit 3ca18922e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 253 additions and 227 deletions

View file

@ -99,6 +99,8 @@ from .util import _VALID_QOS_SCHEMA, valid_publish_topic, valid_subscribe_topic
_LOGGER = logging.getLogger(__name__)
_SENTINEL = object()
DATA_MQTT = "mqtt"
SERVICE_PUBLISH = "publish"
@ -317,6 +319,62 @@ class MqttCommandTemplate:
)
class MqttValueTemplate:
"""Class for rendering MQTT value template with possible json values."""
def __init__(
self,
value_template: template.Template | None,
*,
hass: HomeAssistant | None = None,
entity: Entity | None = None,
config_attributes: template.TemplateVarsType = None,
) -> None:
"""Instantiate a value template."""
self._value_template = value_template
self._config_attributes = config_attributes
if value_template is None:
return
value_template.hass = hass
self._entity = entity
if entity:
value_template.hass = entity.hass
@callback
def async_render_with_possible_json_value(
self,
payload: ReceivePayloadType,
default: ReceivePayloadType | object = _SENTINEL,
variables: template.TemplateVarsType = None,
) -> ReceivePayloadType:
"""Render with possible json value or pass-though a received MQTT value."""
if self._value_template is None:
return payload
values: dict[str, Any] = {}
if variables is not None:
values.update(variables)
if self._config_attributes is not None:
values.update(self._config_attributes)
if self._entity:
values[ATTR_ENTITY_ID] = self._entity.entity_id
values[ATTR_NAME] = self._entity.name
if default == _SENTINEL:
return self._value_template.async_render_with_possible_json_value(
payload, variables=values
)
return self._value_template.async_render_with_possible_json_value(
payload, default, variables=values
)
@dataclass
class MqttServiceInfo(BaseServiceInfo):
"""Prepared info from mqtt entries."""

View file

@ -38,7 +38,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import PLATFORMS, MqttCommandTemplate, subscription
from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription
from .. import mqtt
from .const import (
CONF_COMMAND_TOPIC,
@ -165,9 +165,10 @@ class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity):
return DISCOVERY_SCHEMA
def _setup_from_config(self, config):
value_template = self._config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = self.hass
self._value_template = MqttValueTemplate(
self._config.get(CONF_VALUE_TEMPLATE),
entity=self,
).async_render_with_possible_json_value
self._command_template = MqttCommandTemplate(
self._config[CONF_COMMAND_TEMPLATE], entity=self
).async_render
@ -179,12 +180,7 @@ class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity):
@log_messages(self.hass, self.entity_id)
def message_received(msg):
"""Run when new MQTT message has been received."""
payload = msg.payload
value_template = self._config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
payload = value_template.async_render_with_possible_json_value(
msg.payload, self._state
)
payload = self._value_template(msg.payload)
if payload not in (
STATE_ALARM_DISARMED,
STATE_ALARM_ARMED_HOME,

View file

@ -30,7 +30,7 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util
from . import PLATFORMS, subscription
from . import PLATFORMS, MqttValueTemplate, subscription
from .. import mqtt
from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC, DOMAIN
from .debug_info import log_messages
@ -119,9 +119,10 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity):
return DISCOVERY_SCHEMA
def _setup_from_config(self, config):
value_template = self._config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = self.hass
self._value_template = MqttValueTemplate(
self._config.get(CONF_VALUE_TEMPLATE),
entity=self,
).async_render_with_possible_json_value
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
@ -137,7 +138,6 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity):
@log_messages(self.hass, self.entity_id)
def state_message_received(msg):
"""Handle a new received MQTT state message."""
payload = msg.payload
# auto-expire enabled?
expire_after = self._config.get(CONF_EXPIRE_AFTER)
@ -159,18 +159,14 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity):
self.hass, self._value_is_expired, expiration_at
)
value_template = self._config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
payload = value_template.async_render_with_possible_json_value(
payload, variables={"entity_id": self.entity_id}
)
payload = self._value_template(msg.payload)
if not payload.strip(): # No output from template, ignore
_LOGGER.debug(
"Empty template output for entity: %s with state topic: %s. Payload: '%s', with value template '%s'",
self._config[CONF_NAME],
self._config[CONF_STATE_TOPIC],
msg.payload,
value_template,
self._config.get(CONF_VALUE_TEMPLATE),
)
return
@ -180,8 +176,8 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity):
self._state = False
else: # Payload is not for this entity
template_info = ""
if value_template is not None:
template_info = f", template output: '{payload}', with value template '{str(value_template)}'"
if self._config.get(CONF_VALUE_TEMPLATE) is not None:
template_info = f", template output: '{payload}', with value template '{str(self._config.get(CONF_VALUE_TEMPLATE))}'"
_LOGGER.info(
"No matching payload found for entity: %s with state topic: %s. Payload: '%s'%s",
self._config[CONF_NAME],

View file

@ -56,7 +56,13 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import MQTT_BASE_PLATFORM_SCHEMA, PLATFORMS, MqttCommandTemplate, subscription
from . import (
MQTT_BASE_PLATFORM_SCHEMA,
PLATFORMS,
MqttCommandTemplate,
MqttValueTemplate,
subscription,
)
from .. import mqtt
from .const import CONF_ENCODING, CONF_QOS, CONF_RETAIN, DOMAIN
from .debug_info import log_messages
@ -372,19 +378,20 @@ class MqttClimate(MqttEntity, ClimateEntity):
value_templates = {}
for key in VALUE_TEMPLATE_KEYS:
value_templates[key] = lambda value: value
value_templates[key] = None
if CONF_VALUE_TEMPLATE in config:
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = self.hass
value_templates = {
key: value_template.async_render_with_possible_json_value
for key in VALUE_TEMPLATE_KEYS
key: config.get(CONF_VALUE_TEMPLATE) for key in VALUE_TEMPLATE_KEYS
}
for key in VALUE_TEMPLATE_KEYS & config.keys():
tpl = config[key]
value_templates[key] = tpl.async_render_with_possible_json_value
tpl.hass = self.hass
self._value_templates = value_templates
value_templates[key] = config[key]
self._value_templates = {
key: MqttValueTemplate(
template,
entity=self,
).async_render_with_possible_json_value
for key, template in value_templates.items()
}
command_templates = {}
for key in COMMAND_TEMPLATE_KEYS:

View file

@ -40,7 +40,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import PLATFORMS, MqttCommandTemplate, subscription
from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription
from .. import mqtt
from .const import (
CONF_COMMAND_TOPIC,
@ -303,27 +303,39 @@ class MqttCover(MqttEntity, CoverEntity):
# Force into optimistic tilt mode.
self._tilt_optimistic = True
value_template = self._config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = self.hass
template_config_attributes = {
"position_open": self._config[CONF_POSITION_OPEN],
"position_closed": self._config[CONF_POSITION_CLOSED],
"tilt_min": self._config[CONF_TILT_MIN],
"tilt_max": self._config[CONF_TILT_MAX],
}
self._value_template = MqttValueTemplate(
self._config.get(CONF_VALUE_TEMPLATE),
entity=self,
).async_render_with_possible_json_value
self._set_position_template = MqttCommandTemplate(
self._config.get(CONF_SET_POSITION_TEMPLATE), entity=self
).async_render
get_position_template = self._config.get(CONF_GET_POSITION_TEMPLATE)
if get_position_template is not None:
get_position_template.hass = self.hass
self._get_position_template = MqttValueTemplate(
self._config.get(CONF_GET_POSITION_TEMPLATE),
entity=self,
config_attributes=template_config_attributes,
).async_render_with_possible_json_value
self._set_tilt_template = MqttCommandTemplate(
self._config.get(CONF_TILT_COMMAND_TEMPLATE), entity=self
).async_render
tilt_status_template = self._config.get(CONF_TILT_STATUS_TEMPLATE)
if tilt_status_template is not None:
tilt_status_template.hass = self.hass
self._tilt_status_template = MqttValueTemplate(
self._config.get(CONF_TILT_STATUS_TEMPLATE),
entity=self,
config_attributes=template_config_attributes,
).async_render_with_possible_json_value
async def _subscribe_topics(self): # noqa: C901
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
topics = {}
@ -331,19 +343,7 @@ class MqttCover(MqttEntity, CoverEntity):
@log_messages(self.hass, self.entity_id)
def tilt_message_received(msg):
"""Handle tilt updates."""
payload = msg.payload
template = self._config.get(CONF_TILT_STATUS_TEMPLATE)
if template is not None:
variables = {
"entity_id": self.entity_id,
"position_open": self._config[CONF_POSITION_OPEN],
"position_closed": self._config[CONF_POSITION_CLOSED],
"tilt_min": self._config[CONF_TILT_MIN],
"tilt_max": self._config[CONF_TILT_MAX],
}
payload = template.async_render_with_possible_json_value(
payload, variables=variables
)
payload = self._tilt_status_template(msg.payload)
if not payload:
_LOGGER.debug("Ignoring empty tilt message from '%s'", msg.topic)
@ -355,13 +355,7 @@ class MqttCover(MqttEntity, CoverEntity):
@log_messages(self.hass, self.entity_id)
def state_message_received(msg):
"""Handle new MQTT state messages."""
payload = msg.payload
template = self._config.get(CONF_VALUE_TEMPLATE)
if template is not None:
variables = {"entity_id": self.entity_id}
payload = template.async_render_with_possible_json_value(
payload, variables=variables
)
payload = self._value_template(msg.payload)
if not payload:
_LOGGER.debug("Ignoring empty state message from '%s'", msg.topic)
@ -399,25 +393,10 @@ class MqttCover(MqttEntity, CoverEntity):
@log_messages(self.hass, self.entity_id)
def position_message_received(msg):
"""Handle new MQTT position messages."""
payload = msg.payload
template = self._config.get(CONF_GET_POSITION_TEMPLATE)
if template is not None:
variables = {
"entity_id": self.entity_id,
"position_open": self._config[CONF_POSITION_OPEN],
"position_closed": self._config[CONF_POSITION_CLOSED],
"tilt_min": self._config[CONF_TILT_MIN],
"tilt_max": self._config[CONF_TILT_MAX],
}
payload = template.async_render_with_possible_json_value(
payload, variables=variables
)
payload = self._get_position_template(msg.payload)
if not payload:
_LOGGER.debug(
"Ignoring empty position message from '%s'", msg.topic
)
_LOGGER.debug("Ignoring empty position message from '%s'", msg.topic)
return
try:

View file

@ -18,7 +18,7 @@ from homeassistant.const import (
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from .. import subscription
from .. import MqttValueTemplate, subscription
from ... import mqtt
from ..const import CONF_QOS, CONF_STATE_TOPIC
from ..debug_info import log_messages
@ -73,9 +73,9 @@ class MqttDeviceTracker(MqttEntity, TrackerEntity):
def _setup_from_config(self, config):
"""(Re)Setup the entity."""
value_template = self._config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = self.hass
self._value_template = MqttValueTemplate(
self._config.get(CONF_VALUE_TEMPLATE), entity=self
).async_render_with_possible_json_value
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
@ -84,10 +84,7 @@ class MqttDeviceTracker(MqttEntity, TrackerEntity):
@log_messages(self.hass, self.entity_id)
def message_received(msg):
"""Handle new MQTT messages."""
payload = msg.payload
value_template = self._config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
payload = value_template.async_render_with_possible_json_value(payload)
payload = self._value_template(msg.payload)
if payload == self._config[CONF_PAYLOAD_HOME]:
self._location_name = STATE_HOME
elif payload == self._config[CONF_PAYLOAD_NOT_HOME]:

View file

@ -40,7 +40,7 @@ from homeassistant.util.percentage import (
ranged_value_to_percentage,
)
from . import PLATFORMS, MqttCommandTemplate, subscription
from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription
from .. import mqtt
from .const import (
CONF_COMMAND_TOPIC,
@ -356,11 +356,10 @@ class MqttFan(MqttEntity, FanEntity):
).async_render
for key, tpl in self._value_templates.items():
if tpl is None:
self._value_templates[key] = lambda value: value
else:
tpl.hass = self.hass
self._value_templates[key] = tpl.async_render_with_possible_json_value
self._value_templates[key] = MqttValueTemplate(
tpl,
entity=self,
).async_render_with_possible_json_value
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""

View file

@ -30,7 +30,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import PLATFORMS, MqttCommandTemplate, subscription
from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription
from .. import mqtt
from .const import (
CONF_COMMAND_TOPIC,
@ -262,11 +262,10 @@ class MqttHumidifier(MqttEntity, HumidifierEntity):
).async_render
for key, tpl in self._value_templates.items():
if tpl is None:
self._value_templates[key] = lambda value: value
else:
tpl.hass = self.hass
self._value_templates[key] = tpl.async_render_with_possible_json_value
self._value_templates[key] = MqttValueTemplate(
tpl,
entity=self,
).async_render_with_possible_json_value
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""

View file

@ -51,7 +51,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.restore_state import RestoreEntity
import homeassistant.util.color as color_util
from .. import MqttCommandTemplate, subscription
from .. import MqttCommandTemplate, MqttValueTemplate, subscription
from ... import mqtt
from ..const import (
CONF_COMMAND_TOPIC,
@ -325,12 +325,19 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
value_templates = {}
for key in VALUE_TEMPLATE_KEYS:
value_templates[key] = lambda value, _: value
value_templates[key] = None
if CONF_VALUE_TEMPLATE in config:
value_templates = {
key: config.get(CONF_VALUE_TEMPLATE) for key in VALUE_TEMPLATE_KEYS
}
for key in VALUE_TEMPLATE_KEYS & config.keys():
tpl = config[key]
value_templates[key] = tpl.async_render_with_possible_json_value
tpl.hass = self.hass
self._value_templates = value_templates
value_templates[key] = config[key]
self._value_templates = {
key: MqttValueTemplate(
template, entity=self
).async_render_with_possible_json_value
for key, template in value_templates.items()
}
command_templates = {}
for key in COMMAND_TEMPLATE_KEYS:

View file

@ -33,7 +33,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.restore_state import RestoreEntity
import homeassistant.util.color as color_util
from .. import subscription
from .. import MqttValueTemplate, subscription
from ... import mqtt
from ..const import (
CONF_COMMAND_TOPIC,
@ -160,7 +160,7 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity):
"""(Re)Subscribe to topics."""
for tpl in self._templates.values():
if tpl is not None:
tpl.hass = self.hass
tpl = MqttValueTemplate(tpl, entity=self)
last_state = await self.async_get_last_state()

View file

@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import PLATFORMS, subscription
from . import PLATFORMS, MqttValueTemplate, subscription
from .. import mqtt
from .const import (
CONF_COMMAND_TOPIC,
@ -118,9 +118,10 @@ class MqttLock(MqttEntity, LockEntity):
"""(Re)Setup the entity."""
self._optimistic = config[CONF_OPTIMISTIC]
value_template = self._config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = self.hass
self._value_template = MqttValueTemplate(
self._config.get(CONF_VALUE_TEMPLATE),
entity=self,
).async_render_with_possible_json_value
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
@ -129,10 +130,7 @@ class MqttLock(MqttEntity, LockEntity):
@log_messages(self.hass, self.entity_id)
def message_received(msg):
"""Handle new MQTT messages."""
payload = msg.payload
value_template = self._config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
payload = value_template.async_render_with_possible_json_value(payload)
payload = self._value_template(msg.payload)
if payload == self._config[CONF_STATE_LOCKED]:
self._state = True
elif payload == self._config[CONF_STATE_UNLOCKED]:

View file

@ -38,7 +38,7 @@ from homeassistant.helpers.entity import (
)
from homeassistant.helpers.typing import ConfigType
from . import DATA_MQTT, debug_info, publish, subscription
from . import DATA_MQTT, MqttValueTemplate, debug_info, publish, subscription
from .const import (
ATTR_DISCOVERY_HASH,
ATTR_DISCOVERY_PAYLOAD,
@ -254,17 +254,15 @@ class MqttAttributes(Entity):
async def _attributes_subscribe_topics(self):
"""(Re)Subscribe to topics."""
attr_tpl = self._attributes_config.get(CONF_JSON_ATTRS_TEMPLATE)
if attr_tpl is not None:
attr_tpl.hass = self.hass
attr_tpl = MqttValueTemplate(
self._attributes_config.get(CONF_JSON_ATTRS_TEMPLATE), entity=self
).async_render_with_possible_json_value
@callback
@log_messages(self.hass, self.entity_id)
def attributes_message_received(msg: ReceiveMessage) -> None:
try:
payload = msg.payload
if attr_tpl is not None:
payload = attr_tpl.async_render_with_possible_json_value(payload)
payload = attr_tpl(msg.payload)
json_dict = json.loads(payload) if isinstance(payload, str) else None
if isinstance(json_dict, dict):
filtered_dict = {
@ -356,14 +354,10 @@ class MqttAvailability(Entity):
topic, # pylint: disable=unused-variable
avail_topic_conf,
) in self._avail_topics.items():
tpl = avail_topic_conf[CONF_AVAILABILITY_TEMPLATE]
if tpl is None:
avail_topic_conf[CONF_AVAILABILITY_TEMPLATE] = lambda value: value
else:
tpl.hass = self.hass
avail_topic_conf[
CONF_AVAILABILITY_TEMPLATE
] = tpl.async_render_with_possible_json_value
avail_topic_conf[CONF_AVAILABILITY_TEMPLATE] = MqttValueTemplate(
avail_topic_conf[CONF_AVAILABILITY_TEMPLATE],
entity=self,
).async_render_with_possible_json_value
self._avail_config = config

View file

@ -27,7 +27,7 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import PLATFORMS, MqttCommandTemplate, subscription
from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription
from .. import mqtt
from .const import (
CONF_COMMAND_TOPIC,
@ -157,18 +157,12 @@ class MqttNumber(MqttEntity, NumberEntity, RestoreEntity):
CONF_COMMAND_TEMPLATE: MqttCommandTemplate(
config.get(CONF_COMMAND_TEMPLATE), entity=self
).async_render,
CONF_VALUE_TEMPLATE: config.get(CONF_VALUE_TEMPLATE),
CONF_VALUE_TEMPLATE: MqttValueTemplate(
config.get(CONF_VALUE_TEMPLATE),
entity=self,
).async_render_with_possible_json_value,
}
value_template = self._templates[CONF_VALUE_TEMPLATE]
if value_template is None:
self._templates[CONF_VALUE_TEMPLATE] = lambda value: value
else:
value_template.hass = self.hass
self._templates[
CONF_VALUE_TEMPLATE
] = value_template.async_render_with_possible_json_value
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""

View file

@ -17,7 +17,7 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import PLATFORMS, MqttCommandTemplate, subscription
from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription
from .. import mqtt
from .const import (
CONF_COMMAND_TOPIC,
@ -123,18 +123,12 @@ class MqttSelect(MqttEntity, SelectEntity, RestoreEntity):
CONF_COMMAND_TEMPLATE: MqttCommandTemplate(
config.get(CONF_COMMAND_TEMPLATE), entity=self
).async_render,
CONF_VALUE_TEMPLATE: config.get(CONF_VALUE_TEMPLATE),
CONF_VALUE_TEMPLATE: MqttValueTemplate(
config.get(CONF_VALUE_TEMPLATE),
entity=self,
).async_render_with_possible_json_value,
}
value_template = self._templates[CONF_VALUE_TEMPLATE]
if value_template is None:
self._templates[CONF_VALUE_TEMPLATE] = lambda value: value
else:
value_template.hass = self.hass
self._templates[
CONF_VALUE_TEMPLATE
] = value_template.async_render_with_possible_json_value
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""

View file

@ -32,7 +32,7 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util
from . import PLATFORMS, subscription
from . import PLATFORMS, MqttValueTemplate, subscription
from .. import mqtt
from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC, DOMAIN
from .debug_info import log_messages
@ -167,19 +167,18 @@ class MqttSensor(MqttEntity, SensorEntity):
def _setup_from_config(self, config):
"""(Re)Setup the entity."""
template = self._config.get(CONF_VALUE_TEMPLATE)
if template is not None:
template.hass = self.hass
last_reset_template = self._config.get(CONF_LAST_RESET_VALUE_TEMPLATE)
if last_reset_template is not None:
last_reset_template.hass = self.hass
self._template = MqttValueTemplate(
self._config.get(CONF_VALUE_TEMPLATE), entity=self
).async_render_with_possible_json_value
self._last_reset_template = MqttValueTemplate(
self._config.get(CONF_LAST_RESET_VALUE_TEMPLATE), entity=self
).async_render_with_possible_json_value
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
topics = {}
def _update_state(msg):
payload = msg.payload
# auto-expire enabled?
expire_after = self._config.get(CONF_EXPIRE_AFTER)
if expire_after is not None and expire_after > 0:
@ -198,14 +197,7 @@ class MqttSensor(MqttEntity, SensorEntity):
self.hass, self._value_is_expired, expiration_at
)
template = self._config.get(CONF_VALUE_TEMPLATE)
if template is not None:
variables = {"entity_id": self.entity_id}
payload = template.async_render_with_possible_json_value(
payload,
self._state,
variables=variables,
)
payload = self._template(msg.payload)
if payload is not None and self.device_class in (
SensorDeviceClass.DATE,
@ -221,16 +213,8 @@ class MqttSensor(MqttEntity, SensorEntity):
self._state = payload
def _update_last_reset(msg):
payload = msg.payload
payload = self._last_reset_template(msg.payload)
template = self._config.get(CONF_LAST_RESET_VALUE_TEMPLATE)
if template is not None:
variables = {"entity_id": self.entity_id}
payload = template.async_render_with_possible_json_value(
payload,
self._state,
variables=variables,
)
if not payload:
_LOGGER.debug("Ignoring empty last_reset message from '%s'", msg.topic)
return

View file

@ -24,7 +24,7 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import PLATFORMS, subscription
from . import PLATFORMS, MqttValueTemplate, subscription
from .. import mqtt
from .const import (
CONF_COMMAND_TOPIC,
@ -128,9 +128,9 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity):
self._optimistic = config[CONF_OPTIMISTIC]
template = self._config.get(CONF_VALUE_TEMPLATE)
if template is not None:
template.hass = self.hass
self._value_template = MqttValueTemplate(
self._config.get(CONF_VALUE_TEMPLATE), entity=self
).async_render_with_possible_json_value
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
@ -139,10 +139,7 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity):
@log_messages(self.hass, self.entity_id)
def state_message_received(msg):
"""Handle new MQTT state messages."""
payload = msg.payload
template = self._config.get(CONF_VALUE_TEMPLATE)
if template is not None:
payload = template.async_render_with_possible_json_value(payload)
payload = self._value_template(msg.payload)
if payload == self._state_on:
self._state = True
elif payload == self._state_off:

View file

@ -12,7 +12,7 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_send,
)
from . import subscription
from . import MqttValueTemplate, subscription
from .. import mqtt
from .const import (
ATTR_DISCOVERY_HASH,
@ -143,12 +143,10 @@ class MQTTTagScanner:
)
def _setup_from_config(self, config):
self._value_template = lambda value, error_value: value
if CONF_VALUE_TEMPLATE in config:
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = self.hass
self._value_template = value_template.async_render_with_possible_json_value
self._value_template = MqttValueTemplate(
config.get(CONF_VALUE_TEMPLATE),
hass=self.hass,
).async_render_with_possible_json_value
async def setup(self):
"""Set up the MQTT tag scanner."""
@ -171,7 +169,7 @@ class MQTTTagScanner:
"""Subscribe to MQTT topics."""
async def tag_scanned(msg):
tag_id = self._value_template(msg.payload, error_value="").strip()
tag_id = self._value_template(msg.payload, "").strip()
if not tag_id: # No output from template, ignore
return

View file

@ -24,7 +24,7 @@ from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.icon import icon_for_battery_level
from .. import subscription
from .. import MqttValueTemplate, subscription
from ... import mqtt
from ..const import CONF_COMMAND_TOPIC, CONF_ENCODING, CONF_QOS, CONF_RETAIN
from ..debug_info import log_messages
@ -244,7 +244,7 @@ class MqttVacuum(MqttEntity, VacuumEntity):
"""(Re)Subscribe to topics."""
for tpl in self._templates.values():
if tpl is not None:
tpl.hass = self.hass
tpl = MqttValueTemplate(tpl, entity=self)
@callback
@log_messages(self.hass, self.entity_id)
@ -256,7 +256,7 @@ class MqttVacuum(MqttEntity, VacuumEntity):
):
battery_level = self._templates[
CONF_BATTERY_LEVEL_TEMPLATE
].async_render_with_possible_json_value(msg.payload, error_value=None)
].async_render_with_possible_json_value(msg.payload, None)
if battery_level:
self._battery_level = int(battery_level)
@ -266,7 +266,7 @@ class MqttVacuum(MqttEntity, VacuumEntity):
):
charging = self._templates[
CONF_CHARGING_TEMPLATE
].async_render_with_possible_json_value(msg.payload, error_value=None)
].async_render_with_possible_json_value(msg.payload, None)
if charging:
self._charging = cv.boolean(charging)
@ -276,7 +276,7 @@ class MqttVacuum(MqttEntity, VacuumEntity):
):
cleaning = self._templates[
CONF_CLEANING_TEMPLATE
].async_render_with_possible_json_value(msg.payload, error_value=None)
].async_render_with_possible_json_value(msg.payload, None)
if cleaning:
self._cleaning = cv.boolean(cleaning)
@ -286,7 +286,7 @@ class MqttVacuum(MqttEntity, VacuumEntity):
):
docked = self._templates[
CONF_DOCKED_TEMPLATE
].async_render_with_possible_json_value(msg.payload, error_value=None)
].async_render_with_possible_json_value(msg.payload, None)
if docked:
self._docked = cv.boolean(docked)
@ -296,7 +296,7 @@ class MqttVacuum(MqttEntity, VacuumEntity):
):
error = self._templates[
CONF_ERROR_TEMPLATE
].async_render_with_possible_json_value(msg.payload, error_value=None)
].async_render_with_possible_json_value(msg.payload, None)
if error is not None:
self._error = cv.string(error)
@ -318,7 +318,7 @@ class MqttVacuum(MqttEntity, VacuumEntity):
):
fan_speed = self._templates[
CONF_FAN_SPEED_TEMPLATE
].async_render_with_possible_json_value(msg.payload, error_value=None)
].async_render_with_possible_json_value(msg.payload, None)
if fan_speed:
self._fan_speed = fan_speed

View file

@ -238,6 +238,35 @@ async def test_command_template_variables(hass, mqtt_mock):
assert state.state == "beer"
async def test_value_template_value(hass):
"""Test the rendering of MQTT value template."""
variables = {"id": 1234, "some_var": "beer"}
# test rendering value
tpl = template.Template("{{ value_json.id }}", hass)
val_tpl = mqtt.MqttValueTemplate(tpl, hass=hass)
assert val_tpl.async_render_with_possible_json_value('{"id": 4321}') == "4321"
# test variables at rendering
tpl = template.Template("{{ value_json.id }} {{ some_var }}", hass)
val_tpl = mqtt.MqttValueTemplate(tpl, hass=hass)
assert (
val_tpl.async_render_with_possible_json_value(
'{"id": 4321}', variables=variables
)
== "4321 beer"
)
# test with default value if an error occurs due to an invalid template
tpl = template.Template("{{ value_json.id | as_datetime }}")
val_tpl = mqtt.MqttValueTemplate(tpl, hass=hass)
assert (
val_tpl.async_render_with_possible_json_value('{"otherid": 4321}', "my default")
== "my default"
)
async def test_service_call_without_topic_does_not_publish(hass, mqtt_mock):
"""Test the service call if topic is missing."""
with pytest.raises(vol.Invalid):