diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index edf79ce15a5..ad3e2503527 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -40,6 +40,7 @@ from .const import ( COMMAND_GROUP_LIST, CONF_AUTOMATIC_ADD, CONF_DATA_BITS, + CONF_PROTOCOLS, DATA_RFXOBJECT, DEVICE_PACKET_TYPE_LIGHTING4, EVENT_RFXTRX_EVENT, @@ -123,15 +124,28 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: def _create_rfx(config): """Construct a rfx object based on config.""" + + modes = config.get(CONF_PROTOCOLS) + + if modes: + _LOGGER.debug("Using modes: %s", ",".join(modes)) + else: + _LOGGER.debug("No modes defined, using device configuration") + if config[CONF_PORT] is not None: # If port is set then we create a TCP connection rfx = rfxtrxmod.Connect( (config[CONF_HOST], config[CONF_PORT]), None, transport_protocol=rfxtrxmod.PyNetworkTransport, + modes=modes, ) else: - rfx = rfxtrxmod.Connect(config[CONF_DEVICE], None) + rfx = rfxtrxmod.Connect( + config[CONF_DEVICE], + None, + modes=modes, + ) return rfx diff --git a/homeassistant/components/rfxtrx/config_flow.py b/homeassistant/components/rfxtrx/config_flow.py index 7a842ad470c..754eaeca9ff 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations import copy +import itertools import os from typing import TypedDict, cast @@ -23,6 +24,7 @@ from homeassistant.const import ( CONF_TYPE, ) from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import ( DeviceEntry, DeviceRegistry, @@ -40,6 +42,7 @@ from .const import ( CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_OFF_DELAY, + CONF_PROTOCOLS, CONF_REPLACE_DEVICE, CONF_SIGNAL_REPETITIONS, CONF_VENETIAN_BLIND_MODE, @@ -55,6 +58,8 @@ from .switch import supported as switch_supported CONF_EVENT_CODE = "event_code" CONF_MANUAL_PATH = "Enter Manually" +RECV_MODES = sorted(itertools.chain(*rfxtrxmod.lowlevel.Status.RECMODES)) + class DeviceData(TypedDict): """Dict data representing a device entry.""" @@ -96,6 +101,7 @@ class OptionsFlow(config_entries.OptionsFlow): if user_input is not None: self._global_options = { CONF_AUTOMATIC_ADD: user_input[CONF_AUTOMATIC_ADD], + CONF_PROTOCOLS: user_input[CONF_PROTOCOLS] or None, } if CONF_DEVICE in user_input: entry_id = user_input[CONF_DEVICE] @@ -145,6 +151,10 @@ class OptionsFlow(config_entries.OptionsFlow): CONF_AUTOMATIC_ADD, default=self._config_entry.data[CONF_AUTOMATIC_ADD], ): bool, + vol.Optional( + CONF_PROTOCOLS, + default=self._config_entry.data.get(CONF_PROTOCOLS) or [], + ): cv.multi_select(RECV_MODES), vol.Optional(CONF_EVENT_CODE): str, vol.Optional(CONF_DEVICE): vol.In(configure_devices), } diff --git a/homeassistant/components/rfxtrx/const.py b/homeassistant/components/rfxtrx/const.py index 50cd355c457..74fe1ae2790 100644 --- a/homeassistant/components/rfxtrx/const.py +++ b/homeassistant/components/rfxtrx/const.py @@ -5,6 +5,7 @@ CONF_AUTOMATIC_ADD = "automatic_add" CONF_SIGNAL_REPETITIONS = "signal_repetitions" CONF_OFF_DELAY = "off_delay" CONF_VENETIAN_BLIND_MODE = "venetian_blind_mode" +CONF_PROTOCOLS = "protocols" CONF_REPLACE_DEVICE = "replace_device" diff --git a/homeassistant/components/rfxtrx/strings.json b/homeassistant/components/rfxtrx/strings.json index 542ff9a45cd..f21905c1d21 100644 --- a/homeassistant/components/rfxtrx/strings.json +++ b/homeassistant/components/rfxtrx/strings.json @@ -41,6 +41,7 @@ "data": { "debug": "Enable debugging", "automatic_add": "Enable automatic add", + "protocols": "Protocols", "event_code": "Enter event code to add", "device": "Select device to configure" }, diff --git a/homeassistant/components/rfxtrx/translations/en.json b/homeassistant/components/rfxtrx/translations/en.json index 2728c189010..af042719f77 100644 --- a/homeassistant/components/rfxtrx/translations/en.json +++ b/homeassistant/components/rfxtrx/translations/en.json @@ -61,6 +61,7 @@ "debug": "Enable debugging", "device": "Select device to configure", "event_code": "Enter event code to add", + "protocols": "Protocols", "remove_device": "Select device to delete" }, "title": "Rfxtrx Options" diff --git a/tests/components/rfxtrx/conftest.py b/tests/components/rfxtrx/conftest.py index 70de0be5937..86fec2fdc7f 100644 --- a/tests/components/rfxtrx/conftest.py +++ b/tests/components/rfxtrx/conftest.py @@ -14,13 +14,16 @@ from tests.common import MockConfigEntry, async_fire_time_changed from tests.components.light.conftest import mock_light_profiles # noqa: F401 -def create_rfx_test_cfg(device="abcd", automatic_add=False, devices=None): +def create_rfx_test_cfg( + device="abcd", automatic_add=False, protocols=None, devices=None +): """Create rfxtrx config entry data.""" return { "device": device, "host": None, "port": None, "automatic_add": automatic_add, + "protocols": protocols, "debug": False, "devices": devices, } diff --git a/tests/components/rfxtrx/test_config_flow.py b/tests/components/rfxtrx/test_config_flow.py index bee9ea4880a..82406b8e587 100644 --- a/tests/components/rfxtrx/test_config_flow.py +++ b/tests/components/rfxtrx/test_config_flow.py @@ -11,6 +11,8 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import MockConfigEntry +SOME_PROTOCOLS = ["ac", "arc"] + def serial_connect(self): """Mock a serial connection.""" @@ -286,6 +288,7 @@ async def test_options_global(hass): "port": None, "device": "/dev/tty123", "automatic_add": False, + "protocols": None, "devices": {}, }, unique_id=DOMAIN, @@ -298,7 +301,8 @@ async def test_options_global(hass): assert result["step_id"] == "prompt_options" result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={"automatic_add": True} + result["flow_id"], + user_input={"automatic_add": True, "protocols": SOME_PROTOCOLS}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY @@ -307,6 +311,44 @@ async def test_options_global(hass): assert entry.data["automatic_add"] + assert not set(entry.data["protocols"]) ^ set(SOME_PROTOCOLS) + + +async def test_no_protocols(hass): + """Test we set protocols to None if none are selected.""" + + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "host": None, + "port": None, + "device": "/dev/tty123", + "automatic_add": True, + "protocols": SOME_PROTOCOLS, + "devices": {}, + }, + unique_id=DOMAIN, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == "form" + assert result["step_id"] == "prompt_options" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={"automatic_add": False, "protocols": []}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + await hass.async_block_till_done() + + assert not entry.data["automatic_add"] + + assert entry.data["protocols"] is None + async def test_options_add_device(hass): """Test we can add a device.""" diff --git a/tests/components/rfxtrx/test_init.py b/tests/components/rfxtrx/test_init.py index d5562fa5149..2103db35f13 100644 --- a/tests/components/rfxtrx/test_init.py +++ b/tests/components/rfxtrx/test_init.py @@ -1,14 +1,20 @@ """The tests for the Rfxtrx component.""" from __future__ import annotations -from unittest.mock import call +from unittest.mock import ANY, call, patch +import RFXtrx as rfxtrxmod + +from homeassistant.components.rfxtrx import DOMAIN from homeassistant.components.rfxtrx.const import EVENT_RFXTRX_EVENT from homeassistant.core import callback from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component -from tests.components.rfxtrx.conftest import setup_rfx_test_cfg +from tests.common import MockConfigEntry +from tests.components.rfxtrx.conftest import create_rfx_test_cfg, setup_rfx_test_cfg + +SOME_PROTOCOLS = ["ac", "arc"] async def test_fire_event(hass, rfxtrx): @@ -118,3 +124,31 @@ async def test_ws_device_remove(hass, hass_ws_client): # Verify that the config entry has removed the device assert mock_entry.data["devices"] == {} + + +async def test_connect(hass): + """Test that we attempt to connect to the device.""" + entry_data = create_rfx_test_cfg(device="/dev/ttyUSBfake") + mock_entry = MockConfigEntry(domain="rfxtrx", unique_id=DOMAIN, data=entry_data) + + with patch.object(rfxtrxmod, "Connect") as connect: + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + connect.assert_called_once_with("/dev/ttyUSBfake", ANY, modes=ANY) + + +async def test_connect_with_protocols(hass): + """Test that we attempt to set protocols.""" + entry_data = create_rfx_test_cfg(device="/dev/ttyUSBfake", protocols=SOME_PROTOCOLS) + mock_entry = MockConfigEntry(domain="rfxtrx", unique_id=DOMAIN, data=entry_data) + + with patch.object(rfxtrxmod, "Connect") as connect: + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + connect.assert_called_once_with("/dev/ttyUSBfake", ANY, modes=SOME_PROTOCOLS)