From 45a30546ecf7ad5a9d8e762896679348e3b00588 Mon Sep 17 00:00:00 2001 From: Kevin Addeman Date: Sun, 9 Oct 2022 18:17:06 -0400 Subject: [PATCH] Add support for Homeowner and Phantom Keypads (#79958) --- .../components/lutron_caseta/__init__.py | 22 ++++++++++--- .../lutron_caseta/device_trigger.py | 33 +++++++++++++++++-- .../lutron_caseta/test_device_trigger.py | 31 +++++++++++++++++ 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 321c25b6944..9638f769919 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -232,16 +232,20 @@ def _async_register_button_devices( # use the parent_device for HA device info ha_device = bridge_devices[device["parent_device"]] - if "serial" not in ha_device or ha_device["serial"] in seen: + ha_device_serial = _handle_none_keypad_serial( + ha_device, bridge_device["serial"] + ) + + if "serial" not in ha_device or ha_device_serial in seen: continue - seen.add(ha_device["serial"]) + seen.add(ha_device_serial) area, name = _area_and_name_from_name(ha_device["name"]) device_args: dict[str, Any] = { "name": f"{area} {name}", "manufacturer": MANUFACTURER, "config_entry_id": config_entry_id, - "identifiers": {(DOMAIN, ha_device["serial"])}, + "identifiers": {(DOMAIN, ha_device_serial)}, "model": f"{ha_device['model']} ({ha_device['type']})", "via_device": (DOMAIN, bridge_device["serial"]), } @@ -255,6 +259,10 @@ def _async_register_button_devices( return button_devices_by_dr_id, device_info_by_device_id +def _handle_none_keypad_serial(keypad_device: dict, bridge_serial: int) -> str: + return keypad_device["serial"] or f"{bridge_serial}_{keypad_device['device_id']}" + + def _area_and_name_from_name(device_name: str) -> tuple[str, str]: """Return the area and name from the devices internal name.""" if "_" in device_name: @@ -301,16 +309,20 @@ def _async_subscribe_pico_remote_events( # use the parent_device for HA device info ha_device = bridge_devices[device["parent_device"]] + ha_device_serial = _handle_none_keypad_serial( + ha_device, bridge_devices[BRIDGE_DEVICE_ID]["serial"] + ) + type_ = _lutron_model_to_device_type(ha_device["model"], ha_device["type"]) area, name = _area_and_name_from_name(ha_device["name"]) leap_button_number = device["button_number"] lip_button_number = async_get_lip_button(type_, leap_button_number) - hass_device = dev_reg.async_get_device({(DOMAIN, ha_device["serial"])}) + hass_device = dev_reg.async_get_device({(DOMAIN, ha_device_serial)}) hass.bus.async_fire( LUTRON_CASETA_BUTTON_EVENT, { - ATTR_SERIAL: ha_device["serial"], + ATTR_SERIAL: ha_device_serial, ATTR_TYPE: type_, ATTR_BUTTON_NUMBER: lip_button_number, ATTR_LEAP_BUTTON_NUMBER: leap_button_number, diff --git a/homeassistant/components/lutron_caseta/device_trigger.py b/homeassistant/components/lutron_caseta/device_trigger.py index b9fe89edf7f..30e4e772c99 100644 --- a/homeassistant/components/lutron_caseta/device_trigger.py +++ b/homeassistant/components/lutron_caseta/device_trigger.py @@ -315,6 +315,27 @@ SUNNATA_KEYPAD_4_BUTTON_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend( } ) +HOMEOWNER_KEYPAD_BUTTON_TYPES_TO_LEAP = { + "button_1": 1, + "button_2": 2, + "button_3": 3, + "button_4": 4, + "button_5": 5, + "button_6": 6, + "button_7": 7, +} +HOMEOWNER_KEYPAD_BUTTON_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend( + { + vol.Required(CONF_SUBTYPE): vol.In(HOMEOWNER_KEYPAD_BUTTON_TYPES_TO_LEAP), + } +) + +PHANTOM_KEYPAD_BUTTON_TYPES_TO_LEAP: dict[str, int] = {} +PHANTOM_KEYPAD_BUTTON_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend( + { + vol.Required(CONF_SUBTYPE): vol.In(PHANTOM_KEYPAD_BUTTON_TYPES_TO_LEAP), + } +) DEVICE_TYPE_SCHEMA_MAP = { "Pico2Button": PICO_2_BUTTON_TRIGGER_SCHEMA, @@ -329,6 +350,8 @@ DEVICE_TYPE_SCHEMA_MAP = { "SunnataKeypad_2Button": SUNNATA_KEYPAD_2_BUTTON_TRIGGER_SCHEMA, "SunnataKeypad_3ButtonRaiseLower": SUNNATA_KEYPAD_3_BUTTON_RAISE_LOWER_TRIGGER_SCHEMA, "SunnataKeypad_4Button": SUNNATA_KEYPAD_4_BUTTON_TRIGGER_SCHEMA, + "HomeownerKeypad": HOMEOWNER_KEYPAD_BUTTON_TRIGGER_SCHEMA, + "PhantomKeypad": PHANTOM_KEYPAD_BUTTON_TRIGGER_SCHEMA, } DEVICE_TYPE_SUBTYPE_MAP_TO_LIP = { @@ -356,6 +379,8 @@ DEVICE_TYPE_SUBTYPE_MAP_TO_LEAP = { "SunnataKeypad_2Button": SUNNATA_KEYPAD_2_BUTTON_BUTTON_TYPES_TO_LEAP, "SunnataKeypad_3ButtonRaiseLower": SUNNATA_KEYPAD_3_BUTTON_RAISE_LOWER_BUTTON_TYPES_TO_LEAP, "SunnataKeypad_4Button": SUNNATA_KEYPAD_4_BUTTON_BUTTON_TYPES_TO_LEAP, + "HomeownerKeypad": HOMEOWNER_KEYPAD_BUTTON_TYPES_TO_LEAP, + "PhantomKeypad": PHANTOM_KEYPAD_BUTTON_TYPES_TO_LEAP, } LEAP_TO_DEVICE_TYPE_SUBTYPE_MAP = { @@ -373,6 +398,8 @@ TRIGGER_SCHEMA = vol.Any( SUNNATA_KEYPAD_2_BUTTON_TRIGGER_SCHEMA, SUNNATA_KEYPAD_3_BUTTON_RAISE_LOWER_TRIGGER_SCHEMA, SUNNATA_KEYPAD_4_BUTTON_TRIGGER_SCHEMA, + HOMEOWNER_KEYPAD_BUTTON_TRIGGER_SCHEMA, + PHANTOM_KEYPAD_BUTTON_TRIGGER_SCHEMA, ) @@ -429,9 +456,9 @@ async def async_get_triggers( def _device_model_to_type(device_registry_model: str) -> str: """Convert a lutron_caseta device registry entry model to type.""" - model, p_device_type = device_registry_model.split(" ") - device_type = p_device_type.replace("(", "").replace(")", "") - return _lutron_model_to_device_type(model, device_type) + model_list = device_registry_model.split(" ") + device_type = model_list.pop().replace("(", "").replace(")", "") + return _lutron_model_to_device_type(" ".join(model_list), device_type) def _lutron_model_to_device_type(model: str, device_type: str) -> str: diff --git a/tests/components/lutron_caseta/test_device_trigger.py b/tests/components/lutron_caseta/test_device_trigger.py index 46a26f129c7..991fa191f69 100644 --- a/tests/components/lutron_caseta/test_device_trigger.py +++ b/tests/components/lutron_caseta/test_device_trigger.py @@ -67,6 +67,25 @@ MOCK_BUTTON_DEVICES = [ "model": "RRST-W4B-XX", "serial": 43845547, }, + { + "device_id": "786", + "Name": "Example Homeowner Keypad", + "ID": 3, + "Area": {"Name": "Front Steps"}, + "Buttons": [ + {"Number": 12}, + {"Number": 13}, + {"Number": 14}, + {"Number": 15}, + {"Number": 16}, + {"Number": 17}, + {"Number": 18}, + ], + "leap_name": "Front Steps_Example Homeowner Keypad", + "type": "HomeownerKeypad", + "model": "Homeowner Keypad", + "serial": None, + }, ] @@ -178,6 +197,18 @@ async def test_get_triggers_for_non_button_device(hass, device_reg): assert triggers == [] +async def test_none_serial_keypad(hass, device_reg): + """Test serial assignment for keypads without serials.""" + config_entry_id = await _async_setup_lutron_with_picos(hass, device_reg) + + keypad_device = device_reg.async_get_or_create( + config_entry_id=config_entry_id, + identifiers={(DOMAIN, "1234_786")}, + ) + + assert keypad_device is not None + + async def test_if_fires_on_button_event(hass, calls, device_reg): """Test for press trigger firing.""" await _async_setup_lutron_with_picos(hass, device_reg)