From b717da879feba6746360962c0adc58b5463204e5 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 17 Nov 2022 21:12:27 +0100 Subject: [PATCH] Add support for Mqtt protocol version 5 (#82260) --- homeassistant/components/mqtt/client.py | 20 ++++++++-- homeassistant/components/mqtt/config_flow.py | 6 ++- homeassistant/components/mqtt/const.py | 3 +- tests/components/mqtt/test_config_flow.py | 39 ++++++++++++++++++++ 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index 34384614c20..c94b5d5ec51 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -57,6 +57,7 @@ from .const import ( DEFAULT_QOS, MQTT_CONNECTED, MQTT_DISCONNECTED, + PROTOCOL_5, PROTOCOL_31, ) from .models import ( @@ -272,8 +273,10 @@ class MqttClientSetup: # should be able to optionally rely on MQTT. import paho.mqtt.client as mqtt # pylint: disable=import-outside-toplevel - if config.get(CONF_PROTOCOL, DEFAULT_PROTOCOL) == PROTOCOL_31: + if (protocol := config.get(CONF_PROTOCOL, DEFAULT_PROTOCOL)) == PROTOCOL_31: proto = mqtt.MQTTv31 + elif protocol == PROTOCOL_5: + proto = mqtt.MQTTv5 else: proto = mqtt.MQTTv311 @@ -558,8 +561,9 @@ class MQTT: self, _mqttc: mqtt.Client, _userdata: None, - _flags: dict[str, Any], + _flags: dict[str, int], result_code: int, + properties: mqtt.Properties | None = None, ) -> None: """On connect callback. @@ -677,9 +681,13 @@ class MQTT: _mqttc: mqtt.Client, _userdata: None, mid: int, - _granted_qos: tuple[Any, ...] | None = None, + _granted_qos_reason: tuple[int, ...] | mqtt.ReasonCodes | None = None, + _properties_reason: mqtt.ReasonCodes | None = None, ) -> None: """Publish / Subscribe / Unsubscribe callback.""" + # The callback signature for on_unsubscribe is different from on_subscribe + # see https://github.com/eclipse/paho.mqtt.python/issues/687 + # properties and reasoncodes are not used in Home Assistant self.hass.add_job(self._mqtt_handle_mid, mid) async def _mqtt_handle_mid(self, mid: int) -> None: @@ -695,7 +703,11 @@ class MQTT: self._pending_operations[mid] = asyncio.Event() def _mqtt_on_disconnect( - self, _mqttc: mqtt.Client, _userdata: None, result_code: int + self, + _mqttc: mqtt.Client, + _userdata: None, + result_code: int, + properties: mqtt.Properties | None = None, ) -> None: """Disconnected callback.""" self.connected = False diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 5e5beb2f314..3d5ab99f340 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -683,7 +683,11 @@ def try_connection( result: queue.Queue[bool] = queue.Queue(maxsize=1) def on_connect( - client_: mqtt.Client, userdata: None, flags: dict[str, Any], result_code: int + client_: mqtt.Client, + userdata: None, + flags: dict[str, Any], + result_code: int, + properties: mqtt.Properties | None = None, ) -> None: """Handle connection result.""" result.put(result_code == mqtt.CONNACK_ACCEPTED) diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index dc8aa2f2c74..87da9ab7979 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -46,7 +46,8 @@ DEFAULT_RETAIN = False PROTOCOL_31 = "3.1" PROTOCOL_311 = "3.1.1" -SUPPORTED_PROTOCOLS = [PROTOCOL_31, PROTOCOL_311] +PROTOCOL_5 = "5" +SUPPORTED_PROTOCOLS = [PROTOCOL_31, PROTOCOL_311, PROTOCOL_5] DEFAULT_PORT = 1883 DEFAULT_KEEPALIVE = 60 diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py index eef728664aa..b9fdf0b9c26 100644 --- a/tests/components/mqtt/test_config_flow.py +++ b/tests/components/mqtt/test_config_flow.py @@ -194,6 +194,45 @@ async def test_user_connection_works( assert len(mock_finish_setup.mock_calls) == 1 +async def test_user_v5_connection_works( + hass, mock_try_connection, mock_finish_setup, mqtt_client_mock +): + """Test we can finish a config flow.""" + mock_try_connection.return_value = True + + result = await hass.config_entries.flow.async_init( + "mqtt", context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"broker": "127.0.0.1", "advanced_options": True} + ) + + assert result["step_id"] == "broker" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + mqtt.CONF_BROKER: "another-broker", + mqtt.CONF_PORT: 2345, + mqtt.CONF_PROTOCOL: "5", + }, + ) + assert result["type"] == "create_entry" + assert result["result"].data == { + "broker": "another-broker", + "discovery": True, + "discovery_prefix": "homeassistant", + "port": 2345, + "protocol": "5", + } + # Check we tried the connection + assert len(mock_try_connection.mock_calls) == 1 + # Check config entry got setup + assert len(mock_finish_setup.mock_calls) == 1 + + async def test_user_connection_fails( hass, mock_try_connection_time_out, mock_finish_setup ):