From f72cfef7be56d2e5561fa711273a3837f1ea5646 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 15 Aug 2022 08:26:17 +0200 Subject: [PATCH] Fix MQTT camera encoding (#76124) * Fix MQTT camera encoding * Reduce code * Add test for using image_encoding parameter * Move deprecation check to validation * Dependency * Set correct strings and log warning * Rename constant * Use better issue string identifier * Revert unwanted change to hassio test * Avoid term `deprecated` in issue description * Revert changes using the repairs API * Add a notice when work-a-round will be removed * Update homeassistant/components/mqtt/camera.py Co-authored-by: Erik Montnemery Co-authored-by: Erik Montnemery --- .../components/mqtt/abbreviations.py | 1 + homeassistant/components/mqtt/camera.py | 42 +++++++++-- tests/components/mqtt/test_camera.py | 74 +++++++++++++++++++ 3 files changed, 109 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index ddbced5286d..32b67874a45 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -80,6 +80,7 @@ ABBREVIATIONS = { "hs_stat_t": "hs_state_topic", "hs_val_tpl": "hs_value_template", "ic": "icon", + "img_e": "image_encoding", "init": "initial", "hum_cmd_t": "target_humidity_command_topic", "hum_cmd_tpl": "target_humidity_command_template", diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index f213bec9bb6..61c87e86888 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -3,6 +3,7 @@ from __future__ import annotations from base64 import b64decode import functools +import logging import voluptuous as vol @@ -17,7 +18,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import subscription from .config import MQTT_BASE_SCHEMA -from .const import CONF_ENCODING, CONF_QOS, CONF_TOPIC +from .const import CONF_ENCODING, CONF_QOS, CONF_TOPIC, DEFAULT_ENCODING from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, @@ -29,6 +30,10 @@ from .mixins import ( ) from .util import valid_subscribe_topic +_LOGGER = logging.getLogger(__name__) + +CONF_IMAGE_ENCODING = "image_encoding" + DEFAULT_NAME = "MQTT Camera" MQTT_CAMERA_ATTRIBUTES_BLOCKED = frozenset( @@ -40,20 +45,41 @@ MQTT_CAMERA_ATTRIBUTES_BLOCKED = frozenset( } ) -PLATFORM_SCHEMA_MODERN = MQTT_BASE_SCHEMA.extend( + +# Using CONF_ENCODING to set b64 encoding for images is deprecated as of Home Assistant 2022.9 +# use CONF_IMAGE_ENCODING instead, support for the work-a-round will be removed with Home Assistant 2022.11 +def repair_legacy_encoding(config: ConfigType) -> ConfigType: + """Check incorrect deprecated config of image encoding.""" + if config[CONF_ENCODING] == "b64": + config[CONF_IMAGE_ENCODING] = "b64" + config[CONF_ENCODING] = DEFAULT_ENCODING + _LOGGER.warning( + "Using the `encoding` parameter to set image encoding has been deprecated, use `image_encoding` instead" + ) + return config + + +PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Required(CONF_TOPIC): valid_subscribe_topic, + vol.Optional(CONF_IMAGE_ENCODING): "b64", } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -# Configuring MQTT Camera under the camera platform key is deprecated in HA Core 2022.6 -PLATFORM_SCHEMA = vol.All( - cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_MODERN.schema), - warn_for_legacy_schema(camera.DOMAIN), +PLATFORM_SCHEMA_MODERN = vol.All( + PLATFORM_SCHEMA_BASE.schema, + repair_legacy_encoding, ) -DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA) +# Configuring MQTT Camera under the camera platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_BASE.schema), + warn_for_legacy_schema(camera.DOMAIN), + repair_legacy_encoding, +) + +DISCOVERY_SCHEMA = PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA) async def async_setup_platform( @@ -124,7 +150,7 @@ class MqttCamera(MqttEntity, Camera): @log_messages(self.hass, self.entity_id) def message_received(msg): """Handle new MQTT messages.""" - if self._config[CONF_ENCODING] == "b64": + if CONF_IMAGE_ENCODING in self._config: self._last_image = b64decode(msg.payload) else: self._last_image = msg.payload diff --git a/tests/components/mqtt/test_camera.py b/tests/components/mqtt/test_camera.py index c6d116b6a74..a76025a608a 100644 --- a/tests/components/mqtt/test_camera.py +++ b/tests/components/mqtt/test_camera.py @@ -110,6 +110,80 @@ async def test_run_camera_b64_encoded( assert body == "grass" +# Using CONF_ENCODING to set b64 encoding for images is deprecated Home Assistant 2022.9, use CONF_IMAGE_ENCODING instead +async def test_legacy_camera_b64_encoded_with_availability( + hass, hass_client_no_auth, mqtt_mock_entry_with_yaml_config +): + """Test availability works if b64 encoding (legacy mode) is turned on.""" + topic = "test/camera" + topic_availability = "test/camera_availability" + await async_setup_component( + hass, + "camera", + { + "camera": { + "platform": "mqtt", + "topic": topic, + "name": "Test Camera", + "encoding": "b64", + "availability": {"topic": topic_availability}, + } + }, + ) + await hass.async_block_till_done() + await mqtt_mock_entry_with_yaml_config() + + # Make sure we are available + async_fire_mqtt_message(hass, topic_availability, "online") + + url = hass.states.get("camera.test_camera").attributes["entity_picture"] + + async_fire_mqtt_message(hass, topic, b64encode(b"grass")) + + client = await hass_client_no_auth() + resp = await client.get(url) + assert resp.status == HTTPStatus.OK + body = await resp.text() + assert body == "grass" + + +async def test_camera_b64_encoded_with_availability( + hass, hass_client_no_auth, mqtt_mock_entry_with_yaml_config +): + """Test availability works if b64 encoding is turned on.""" + topic = "test/camera" + topic_availability = "test/camera_availability" + await async_setup_component( + hass, + "camera", + { + "camera": { + "platform": "mqtt", + "topic": topic, + "name": "Test Camera", + "encoding": "utf-8", + "image_encoding": "b64", + "availability": {"topic": topic_availability}, + } + }, + ) + await hass.async_block_till_done() + await mqtt_mock_entry_with_yaml_config() + + # Make sure we are available + async_fire_mqtt_message(hass, topic_availability, "online") + + url = hass.states.get("camera.test_camera").attributes["entity_picture"] + + async_fire_mqtt_message(hass, topic, b64encode(b"grass")) + + client = await hass_client_no_auth() + resp = await client.get(url) + assert resp.status == HTTPStatus.OK + body = await resp.text() + assert body == "grass" + + async def test_availability_when_connection_lost( hass, mqtt_mock_entry_with_yaml_config ):