Add pico remote support to non-pro lutron caseta bridges (#61032)

pull/62364/head
J. Nick Koston 2021-12-19 01:41:02 -06:00 committed by GitHub
parent 832184bacd
commit e834382b9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 234 additions and 146 deletions

View File

@ -1,19 +1,19 @@
"""Component for interacting with a Lutron Caseta system.""" """Component for interacting with a Lutron Caseta system."""
from __future__ import annotations
import asyncio import asyncio
import contextlib import contextlib
import logging import logging
import ssl import ssl
from aiolip import LIP
from aiolip.data import LIPMode
from aiolip.protocol import LIP_BUTTON_PRESS
import async_timeout import async_timeout
from pylutron_caseta import BUTTON_STATUS_PRESSED
from pylutron_caseta.smartbridge import Smartbridge from pylutron_caseta.smartbridge import Smartbridge
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_HOST, Platform from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -26,12 +26,12 @@ from .const import (
ATTR_AREA_NAME, ATTR_AREA_NAME,
ATTR_BUTTON_NUMBER, ATTR_BUTTON_NUMBER,
ATTR_DEVICE_NAME, ATTR_DEVICE_NAME,
ATTR_LEAP_BUTTON_NUMBER,
ATTR_SERIAL, ATTR_SERIAL,
ATTR_TYPE, ATTR_TYPE,
BRIDGE_DEVICE, BRIDGE_DEVICE,
BRIDGE_DEVICE_ID, BRIDGE_DEVICE_ID,
BRIDGE_LEAP, BRIDGE_LEAP,
BRIDGE_LIP,
BRIDGE_TIMEOUT, BRIDGE_TIMEOUT,
BUTTON_DEVICES, BUTTON_DEVICES,
CONF_CA_CERTS, CONF_CA_CERTS,
@ -41,6 +41,10 @@ from .const import (
LUTRON_CASETA_BUTTON_EVENT, LUTRON_CASETA_BUTTON_EVENT,
MANUFACTURER, MANUFACTURER,
) )
from .device_trigger import (
DEVICE_TYPE_SUBTYPE_MAP_TO_LIP,
LEAP_TO_DEVICE_TYPE_SUBTYPE_MAP,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -97,8 +101,11 @@ async def async_setup(hass, base_config):
return True return True
async def async_setup_entry(hass, config_entry): async def async_setup_entry(
hass: HomeAssistant, config_entry: config_entries.ConfigEntry
) -> bool:
"""Set up a bridge from a config entry.""" """Set up a bridge from a config entry."""
entry_id = config_entry.entry_id
host = config_entry.data[CONF_HOST] host = config_entry.data[CONF_HOST]
keyfile = hass.config.path(config_entry.data[CONF_KEYFILE]) keyfile = hass.config.path(config_entry.data[CONF_KEYFILE])
certfile = hass.config.path(config_entry.data[CONF_CERTFILE]) certfile = hass.config.path(config_entry.data[CONF_CERTFILE])
@ -130,85 +137,30 @@ async def async_setup_entry(hass, config_entry):
devices = bridge.get_devices() devices = bridge.get_devices()
bridge_device = devices[BRIDGE_DEVICE_ID] bridge_device = devices[BRIDGE_DEVICE_ID]
_async_register_bridge_device(hass, config_entry.entry_id, bridge_device) buttons = bridge.buttons
_async_register_bridge_device(hass, entry_id, bridge_device)
button_devices = _async_register_button_devices(
hass, entry_id, bridge_device, buttons
)
_async_subscribe_pico_remote_events(hass, bridge, buttons)
# Store this bridge (keyed by entry_id) so it can be retrieved by the # Store this bridge (keyed by entry_id) so it can be retrieved by the
# platforms we're setting up. # platforms we're setting up.
hass.data[DOMAIN][config_entry.entry_id] = { hass.data[DOMAIN][entry_id] = {
BRIDGE_LEAP: bridge, BRIDGE_LEAP: bridge,
BRIDGE_DEVICE: bridge_device, BRIDGE_DEVICE: bridge_device,
BUTTON_DEVICES: {}, BUTTON_DEVICES: button_devices,
BRIDGE_LIP: None,
} }
if bridge.lip_devices:
# If the bridge also supports LIP (Lutron Integration Protocol)
# we can fire events when pico buttons are pressed to allow
# pico remotes to control other devices.
await async_setup_lip(hass, config_entry, bridge.lip_devices)
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
return True return True
async def async_setup_lip(hass, config_entry, lip_devices):
"""Connect to the bridge via Lutron Integration Protocol to watch for pico remotes."""
host = config_entry.data[CONF_HOST]
config_entry_id = config_entry.entry_id
data = hass.data[DOMAIN][config_entry_id]
bridge_device = data[BRIDGE_DEVICE]
bridge = data[BRIDGE_LEAP]
lip = LIP()
try:
await lip.async_connect(host)
except asyncio.TimeoutError:
_LOGGER.warning(
"Failed to connect to via LIP at %s:23, Pico and Shade remotes will not be available; "
"Enable Telnet Support in the Lutron app under Settings >> Advanced >> Integration",
host,
)
return
_LOGGER.debug("Connected to Lutron Caseta bridge via LIP at %s:23", host)
button_devices_by_lip_id = _async_merge_lip_leap_data(lip_devices, bridge)
button_devices_by_dr_id = _async_register_button_devices(
hass, config_entry_id, bridge_device, button_devices_by_lip_id
)
_async_subscribe_pico_remote_events(hass, lip, button_devices_by_lip_id)
data[BUTTON_DEVICES] = button_devices_by_dr_id
data[BRIDGE_LIP] = lip
@callback @callback
def _async_merge_lip_leap_data(lip_devices, bridge): def _async_register_bridge_device(
"""Merge the leap data into the lip data.""" hass: HomeAssistant, config_entry_id: str, bridge_device: dict
sensor_devices = bridge.get_devices_by_domain("sensor") ) -> None:
button_devices_by_id = {
id: device for id, device in lip_devices.items() if "Buttons" in device
}
sensor_devices_by_name = {device["name"]: device for device in sensor_devices}
# Add the leap data into the lip data
# so we know the type, model, and serial
for device in button_devices_by_id.values():
area = device.get("Area", {}).get("Name", "")
name = device["Name"]
leap_name = f"{area}_{name}"
device["leap_name"] = leap_name
leap_device_data = sensor_devices_by_name.get(leap_name)
if leap_device_data is None:
continue
for key in ("type", "model", "serial"):
if (val := leap_device_data.get(key)) is not None:
device[key] = val
_LOGGER.debug("Button Devices: %s", button_devices_by_id)
return button_devices_by_id
@callback
def _async_register_bridge_device(hass, config_entry_id, bridge_device):
"""Register the bridge device in the device registry.""" """Register the bridge device in the device registry."""
device_registry = dr.async_get(hass) device_registry = dr.async_get(hass)
device_registry.async_get_or_create( device_registry.async_get_or_create(
@ -217,24 +169,30 @@ def _async_register_bridge_device(hass, config_entry_id, bridge_device):
config_entry_id=config_entry_id, config_entry_id=config_entry_id,
identifiers={(DOMAIN, bridge_device["serial"])}, identifiers={(DOMAIN, bridge_device["serial"])},
model=f"{bridge_device['model']} ({bridge_device['type']})", model=f"{bridge_device['model']} ({bridge_device['type']})",
configuration_url="https://device-login.lutron.com",
) )
@callback @callback
def _async_register_button_devices( def _async_register_button_devices(
hass, config_entry_id, bridge_device, button_devices_by_id hass: HomeAssistant,
): config_entry_id: str,
bridge_device,
button_devices_by_id: dict[int, dict],
) -> dict[str, dr.DeviceEntry]:
"""Register button devices (Pico Remotes) in the device registry.""" """Register button devices (Pico Remotes) in the device registry."""
device_registry = dr.async_get(hass) device_registry = dr.async_get(hass)
button_devices_by_dr_id = {} button_devices_by_dr_id = {}
seen = set()
for device in button_devices_by_id.values(): for device in button_devices_by_id.values():
if "serial" not in device: if "serial" not in device or device["serial"] in seen:
continue continue
seen.add(device["serial"])
dr_device = device_registry.async_get_or_create( dr_device = device_registry.async_get_or_create(
name=device["leap_name"], name=device["name"],
suggested_area=device["leap_name"].split("_")[0], suggested_area=device["name"].split("_")[0],
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,
config_entry_id=config_entry_id, config_entry_id=config_entry_id,
identifiers={(DOMAIN, device["serial"])}, identifiers={(DOMAIN, device["serial"])},
@ -248,54 +206,74 @@ def _async_register_button_devices(
@callback @callback
def _async_subscribe_pico_remote_events(hass, lip, button_devices_by_id): def _async_subscribe_pico_remote_events(
hass: HomeAssistant,
bridge_device: Smartbridge,
button_devices_by_id: dict[int, dict],
):
"""Subscribe to lutron events.""" """Subscribe to lutron events."""
@callback @callback
def _async_lip_event(lip_message): def _async_button_event(button_id, event_type):
if lip_message.mode != LIPMode.DEVICE: device = button_devices_by_id.get(button_id)
return
device = button_devices_by_id.get(lip_message.integration_id)
if not device: if not device:
return return
if lip_message.value == LIP_BUTTON_PRESS: if event_type == BUTTON_STATUS_PRESSED:
action = ACTION_PRESS action = ACTION_PRESS
else: else:
action = ACTION_RELEASE action = ACTION_RELEASE
type_ = device["type"]
name = device["name"]
button_number = device["button_number"]
# The original implementation used LIP instead of LEAP
# so we need to convert the button number to maintain compat
sub_type_to_lip_button = DEVICE_TYPE_SUBTYPE_MAP_TO_LIP[type_]
leap_button_to_sub_type = LEAP_TO_DEVICE_TYPE_SUBTYPE_MAP[type_]
if (sub_type := leap_button_to_sub_type.get(button_number)) is None:
_LOGGER.error(
"Unknown LEAP button number %s is not in %s for %s (%s)",
button_number,
leap_button_to_sub_type,
name,
type_,
)
return
lip_button_number = sub_type_to_lip_button[sub_type]
hass.bus.async_fire( hass.bus.async_fire(
LUTRON_CASETA_BUTTON_EVENT, LUTRON_CASETA_BUTTON_EVENT,
{ {
ATTR_SERIAL: device.get("serial"), ATTR_SERIAL: device["serial"],
ATTR_TYPE: device.get("type"), ATTR_TYPE: type_,
ATTR_BUTTON_NUMBER: lip_message.action_number, ATTR_BUTTON_NUMBER: lip_button_number,
ATTR_DEVICE_NAME: device["Name"], ATTR_LEAP_BUTTON_NUMBER: button_number,
ATTR_AREA_NAME: device.get("Area", {}).get("Name"), ATTR_DEVICE_NAME: name,
ATTR_AREA_NAME: name.split("_")[0],
ATTR_ACTION: action, ATTR_ACTION: action,
}, },
) )
lip.subscribe(_async_lip_event) for button_id in button_devices_by_id:
bridge_device.add_button_subscriber(
asyncio.create_task(lip.async_run()) str(button_id),
lambda event_type, button_id=button_id: _async_button_event(
button_id, event_type
async def async_unload_entry(hass, config_entry): ),
"""Unload the bridge bridge from a config entry."""
data = hass.data[DOMAIN][config_entry.entry_id]
data[BRIDGE_LEAP].close()
if data[BRIDGE_LIP]:
await data[BRIDGE_LIP].async_stop()
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
) )
if unload_ok:
hass.data[DOMAIN].pop(config_entry.entry_id)
async def async_unload_entry(
hass: HomeAssistant, entry: config_entries.ConfigEntry
) -> bool:
"""Unload the bridge bridge from a config entry."""
data = hass.data[DOMAIN][entry.entry_id]
smartbridge: Smartbridge = data[BRIDGE_LEAP]
await smartbridge.close()
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok return unload_ok

View File

@ -11,7 +11,6 @@ ERROR_CANNOT_CONNECT = "cannot_connect"
ABORT_REASON_CANNOT_CONNECT = "cannot_connect" ABORT_REASON_CANNOT_CONNECT = "cannot_connect"
BRIDGE_LEAP = "leap" BRIDGE_LEAP = "leap"
BRIDGE_LIP = "lip"
BRIDGE_DEVICE = "bridge_device" BRIDGE_DEVICE = "bridge_device"
BUTTON_DEVICES = "button_devices" BUTTON_DEVICES = "button_devices"
LUTRON_CASETA_BUTTON_EVENT = "lutron_caseta_button_event" LUTRON_CASETA_BUTTON_EVENT = "lutron_caseta_button_event"
@ -22,7 +21,8 @@ MANUFACTURER = "Lutron Electronics Co., Inc"
ATTR_SERIAL = "serial" ATTR_SERIAL = "serial"
ATTR_TYPE = "type" ATTR_TYPE = "type"
ATTR_BUTTON_NUMBER = "button_number" ATTR_LEAP_BUTTON_NUMBER = "leap_button_number"
ATTR_BUTTON_NUMBER = "button_number" # LIP button number
ATTR_DEVICE_NAME = "device_name" ATTR_DEVICE_NAME = "device_name"
ATTR_AREA_NAME = "area_name" ATTR_AREA_NAME = "area_name"
ATTR_ACTION = "action" ATTR_ACTION = "action"

View File

@ -46,106 +46,180 @@ LUTRON_BUTTON_TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
) )
PICO_2_BUTTON_BUTTON_TYPES = { PICO_2_BUTTON_BUTTON_TYPES_TO_LIP = {
"on": 2, "on": 2,
"off": 4, "off": 4,
} }
PICO_2_BUTTON_BUTTON_TYPES_TO_LEAP = {
"on": 0,
"off": 2,
}
LEAP_TO_PICO_2_BUTTON_BUTTON_TYPES = {
v: k for k, v in PICO_2_BUTTON_BUTTON_TYPES_TO_LEAP.items()
}
PICO_2_BUTTON_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend( PICO_2_BUTTON_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend(
{ {
vol.Required(CONF_SUBTYPE): vol.In(PICO_2_BUTTON_BUTTON_TYPES), vol.Required(CONF_SUBTYPE): vol.In(PICO_2_BUTTON_BUTTON_TYPES_TO_LIP),
} }
) )
PICO_2_BUTTON_RAISE_LOWER_BUTTON_TYPES = { PICO_2_BUTTON_RAISE_LOWER_BUTTON_TYPES_TO_LIP = {
"on": 2, "on": 2,
"off": 4, "off": 4,
"raise": 5, "raise": 5,
"lower": 6, "lower": 6,
} }
PICO_2_BUTTON_RAISE_LOWER_BUTTON_TYPES_TO_LEAP = {
"on": 0,
"off": 2,
"raise": 3,
"lower": 4,
}
LEAP_TO_PICO_2_BUTTON_RAISE_LOWER_BUTTON_TYPES = {
v: k for k, v in PICO_2_BUTTON_RAISE_LOWER_BUTTON_TYPES_TO_LEAP.items()
}
PICO_2_BUTTON_RAISE_LOWER_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend( PICO_2_BUTTON_RAISE_LOWER_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend(
{ {
vol.Required(CONF_SUBTYPE): vol.In(PICO_2_BUTTON_RAISE_LOWER_BUTTON_TYPES), vol.Required(CONF_SUBTYPE): vol.In(
PICO_2_BUTTON_RAISE_LOWER_BUTTON_TYPES_TO_LIP
),
} }
) )
PICO_3_BUTTON_BUTTON_TYPES = { PICO_3_BUTTON_BUTTON_TYPES_TO_LIP = {
"on": 2, "on": 2,
"stop": 3, "stop": 3,
"off": 4, "off": 4,
} }
PICO_3_BUTTON_BUTTON_TYPES_TO_LEAP = {
"on": 0,
"stop": 1,
"off": 2,
}
LEAP_TO_PICO_3_BUTTON_BUTTON_TYPES = {
v: k for k, v in PICO_3_BUTTON_BUTTON_TYPES_TO_LEAP.items()
}
PICO_3_BUTTON_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend( PICO_3_BUTTON_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend(
{ {
vol.Required(CONF_SUBTYPE): vol.In(PICO_3_BUTTON_BUTTON_TYPES), vol.Required(CONF_SUBTYPE): vol.In(PICO_3_BUTTON_BUTTON_TYPES_TO_LIP),
} }
) )
PICO_3_BUTTON_RAISE_LOWER_BUTTON_TYPES = { PICO_3_BUTTON_RAISE_LOWER_BUTTON_TYPES_TO_LIP = {
"on": 2, "on": 2,
"stop": 3, "stop": 3,
"off": 4, "off": 4,
"raise": 5, "raise": 5,
"lower": 6, "lower": 6,
} }
PICO_3_BUTTON_RAISE_LOWER_BUTTON_TYPES_TO_LEAP = {
"on": 0,
"stop": 1,
"off": 2,
"raise": 3,
"lower": 4,
}
LEAP_TO_PICO_3_BUTTON_RAISE_LOWER_BUTTON_TYPES = {
v: k for k, v in PICO_3_BUTTON_RAISE_LOWER_BUTTON_TYPES_TO_LEAP.items()
}
PICO_3_BUTTON_RAISE_LOWER_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend( PICO_3_BUTTON_RAISE_LOWER_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend(
{ {
vol.Required(CONF_SUBTYPE): vol.In(PICO_3_BUTTON_RAISE_LOWER_BUTTON_TYPES), vol.Required(CONF_SUBTYPE): vol.In(
PICO_3_BUTTON_RAISE_LOWER_BUTTON_TYPES_TO_LIP
),
} }
) )
PICO_4_BUTTON_BUTTON_TYPES = { PICO_4_BUTTON_BUTTON_TYPES_TO_LIP = {
"button_1": 8, "button_1": 8,
"button_2": 9, "button_2": 9,
"button_3": 10, "button_3": 10,
"button_4": 11, "button_4": 11,
} }
PICO_4_BUTTON_BUTTON_TYPES_TO_LEAP = {
"button_1": 1,
"button_2": 2,
"button_3": 3,
"button_4": 4,
}
LEAP_TO_PICO_4_BUTTON_BUTTON_TYPES = {
v: k for k, v in PICO_4_BUTTON_BUTTON_TYPES_TO_LEAP.items()
}
PICO_4_BUTTON_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend( PICO_4_BUTTON_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend(
{ {
vol.Required(CONF_SUBTYPE): vol.In(PICO_4_BUTTON_BUTTON_TYPES), vol.Required(CONF_SUBTYPE): vol.In(PICO_4_BUTTON_BUTTON_TYPES_TO_LIP),
} }
) )
PICO_4_BUTTON_ZONE_BUTTON_TYPES = { PICO_4_BUTTON_ZONE_BUTTON_TYPES_TO_LIP = {
"on": 8, "on": 8,
"raise": 9, "raise": 9,
"lower": 10, "lower": 10,
"off": 11, "off": 11,
} }
PICO_4_BUTTON_ZONE_BUTTON_TYPES_TO_LEAP = {
"on": 1,
"raise": 2,
"lower": 3,
"off": 4,
}
LEAP_TO_PICO_4_BUTTON_ZONE_BUTTON_TYPES = {
v: k for k, v in PICO_4_BUTTON_ZONE_BUTTON_TYPES_TO_LEAP.items()
}
PICO_4_BUTTON_ZONE_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend( PICO_4_BUTTON_ZONE_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend(
{ {
vol.Required(CONF_SUBTYPE): vol.In(PICO_4_BUTTON_ZONE_BUTTON_TYPES), vol.Required(CONF_SUBTYPE): vol.In(PICO_4_BUTTON_ZONE_BUTTON_TYPES_TO_LIP),
} }
) )
PICO_4_BUTTON_SCENE_BUTTON_TYPES = { PICO_4_BUTTON_SCENE_BUTTON_TYPES_TO_LIP = {
"button_1": 8, "button_1": 8,
"button_2": 9, "button_2": 9,
"button_3": 10, "button_3": 10,
"off": 11, "off": 11,
} }
PICO_4_BUTTON_SCENE_BUTTON_TYPES_TO_LEAP = {
"button_1": 1,
"button_2": 2,
"button_3": 3,
"off": 4,
}
LEAP_TO_PICO_4_BUTTON_SCENE_BUTTON_TYPES = {
v: k for k, v in PICO_4_BUTTON_SCENE_BUTTON_TYPES_TO_LEAP.items()
}
PICO_4_BUTTON_SCENE_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend( PICO_4_BUTTON_SCENE_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend(
{ {
vol.Required(CONF_SUBTYPE): vol.In(PICO_4_BUTTON_SCENE_BUTTON_TYPES), vol.Required(CONF_SUBTYPE): vol.In(PICO_4_BUTTON_SCENE_BUTTON_TYPES_TO_LIP),
} }
) )
PICO_4_BUTTON_2_GROUP_BUTTON_TYPES = { PICO_4_BUTTON_2_GROUP_BUTTON_TYPES_TO_LIP = {
"group_1_button_1": 8, "group_1_button_1": 8,
"group_1_button_2": 9, "group_1_button_2": 9,
"group_2_button_1": 10, "group_2_button_1": 10,
"group_2_button_2": 11, "group_2_button_2": 11,
} }
PICO_4_BUTTON_2_GROUP_BUTTON_TYPES_TO_LEAP = {
"group_1_button_1": 1,
"group_1_button_2": 2,
"group_2_button_1": 3,
"group_2_button_2": 4,
}
LEAP_TO_PICO_4_BUTTON_2_GROUP_BUTTON_TYPES = {
v: k for k, v in PICO_4_BUTTON_2_GROUP_BUTTON_TYPES_TO_LEAP.items()
}
PICO_4_BUTTON_2_GROUP_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend( PICO_4_BUTTON_2_GROUP_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend(
{ {
vol.Required(CONF_SUBTYPE): vol.In(PICO_4_BUTTON_2_GROUP_BUTTON_TYPES), vol.Required(CONF_SUBTYPE): vol.In(PICO_4_BUTTON_2_GROUP_BUTTON_TYPES_TO_LIP),
} }
) )
FOUR_GROUP_REMOTE_BUTTON_TYPES = { FOUR_GROUP_REMOTE_BUTTON_TYPES_TO_LIP = {
"open_all": 2, "open_all": 2,
"stop_all": 3, "stop_all": 3,
"close_all": 4, "close_all": 4,
@ -172,9 +246,39 @@ FOUR_GROUP_REMOTE_BUTTON_TYPES = {
"raise_4": 37, "raise_4": 37,
"lower_4": 38, "lower_4": 38,
} }
FOUR_GROUP_REMOTE_BUTTON_TYPES_TO_LEAP = {
"open_all": 0,
"stop_all": 1,
"close_all": 2,
"raise_all": 3,
"lower_all": 4,
"open_1": 5,
"stop_1": 6,
"close_1": 7,
"raise_1": 8,
"lower_1": 9,
"open_2": 10,
"stop_2": 11,
"close_2": 12,
"raise_2": 13,
"lower_2": 14,
"open_3": 15,
"stop_3": 16,
"close_3": 17,
"raise_3": 18,
"lower_3": 19,
"open_4": 20,
"stop_4": 21,
"close_4": 22,
"raise_4": 23,
"lower_4": 24,
}
LEAP_TO_FOUR_GROUP_REMOTE_BUTTON_TYPES = {
v: k for k, v in FOUR_GROUP_REMOTE_BUTTON_TYPES_TO_LEAP.items()
}
FOUR_GROUP_REMOTE_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend( FOUR_GROUP_REMOTE_TRIGGER_SCHEMA = LUTRON_BUTTON_TRIGGER_SCHEMA.extend(
{ {
vol.Required(CONF_SUBTYPE): vol.In(FOUR_GROUP_REMOTE_BUTTON_TYPES), vol.Required(CONF_SUBTYPE): vol.In(FOUR_GROUP_REMOTE_BUTTON_TYPES_TO_LIP),
} }
) )
@ -190,16 +294,28 @@ DEVICE_TYPE_SCHEMA_MAP = {
"FourGroupRemote": FOUR_GROUP_REMOTE_TRIGGER_SCHEMA, "FourGroupRemote": FOUR_GROUP_REMOTE_TRIGGER_SCHEMA,
} }
DEVICE_TYPE_SUBTYPE_MAP = { DEVICE_TYPE_SUBTYPE_MAP_TO_LIP = {
"Pico2Button": PICO_2_BUTTON_BUTTON_TYPES, "Pico2Button": PICO_2_BUTTON_BUTTON_TYPES_TO_LIP,
"Pico2ButtonRaiseLower": PICO_2_BUTTON_RAISE_LOWER_BUTTON_TYPES, "Pico2ButtonRaiseLower": PICO_2_BUTTON_RAISE_LOWER_BUTTON_TYPES_TO_LIP,
"Pico3Button": PICO_3_BUTTON_BUTTON_TYPES, "Pico3Button": PICO_3_BUTTON_BUTTON_TYPES_TO_LIP,
"Pico3ButtonRaiseLower": PICO_3_BUTTON_RAISE_LOWER_BUTTON_TYPES, "Pico3ButtonRaiseLower": PICO_3_BUTTON_RAISE_LOWER_BUTTON_TYPES_TO_LIP,
"Pico4Button": PICO_4_BUTTON_BUTTON_TYPES, "Pico4Button": PICO_4_BUTTON_BUTTON_TYPES_TO_LIP,
"Pico4ButtonScene": PICO_4_BUTTON_SCENE_BUTTON_TYPES, "Pico4ButtonScene": PICO_4_BUTTON_SCENE_BUTTON_TYPES_TO_LIP,
"Pico4ButtonZone": PICO_4_BUTTON_ZONE_BUTTON_TYPES, "Pico4ButtonZone": PICO_4_BUTTON_ZONE_BUTTON_TYPES_TO_LIP,
"Pico4Button2Group": PICO_4_BUTTON_2_GROUP_BUTTON_TYPES, "Pico4Button2Group": PICO_4_BUTTON_2_GROUP_BUTTON_TYPES_TO_LIP,
"FourGroupRemote": FOUR_GROUP_REMOTE_BUTTON_TYPES, "FourGroupRemote": FOUR_GROUP_REMOTE_BUTTON_TYPES_TO_LIP,
}
LEAP_TO_DEVICE_TYPE_SUBTYPE_MAP = {
"Pico2Button": LEAP_TO_PICO_2_BUTTON_BUTTON_TYPES,
"Pico2ButtonRaiseLower": LEAP_TO_PICO_2_BUTTON_RAISE_LOWER_BUTTON_TYPES,
"Pico3Button": LEAP_TO_PICO_3_BUTTON_BUTTON_TYPES,
"Pico3ButtonRaiseLower": LEAP_TO_PICO_3_BUTTON_RAISE_LOWER_BUTTON_TYPES,
"Pico4Button": LEAP_TO_PICO_4_BUTTON_BUTTON_TYPES,
"Pico4ButtonScene": LEAP_TO_PICO_4_BUTTON_SCENE_BUTTON_TYPES,
"Pico4ButtonZone": LEAP_TO_PICO_4_BUTTON_ZONE_BUTTON_TYPES,
"Pico4Button2Group": LEAP_TO_PICO_4_BUTTON_2_GROUP_BUTTON_TYPES,
"FourGroupRemote": LEAP_TO_FOUR_GROUP_REMOTE_BUTTON_TYPES,
} }
TRIGGER_SCHEMA = vol.Any( TRIGGER_SCHEMA = vol.Any(
@ -238,7 +354,7 @@ async def async_get_triggers(
if not (device := get_button_device_by_dr_id(hass, device_id)): if not (device := get_button_device_by_dr_id(hass, device_id)):
raise InvalidDeviceAutomationConfig(f"Device not found: {device_id}") raise InvalidDeviceAutomationConfig(f"Device not found: {device_id}")
valid_buttons = DEVICE_TYPE_SUBTYPE_MAP.get(device["type"], []) valid_buttons = DEVICE_TYPE_SUBTYPE_MAP_TO_LIP.get(device["type"], [])
for trigger in SUPPORTED_INPUTS_EVENTS_TYPES: for trigger in SUPPORTED_INPUTS_EVENTS_TYPES:
for subtype in valid_buttons: for subtype in valid_buttons:
@ -273,7 +389,7 @@ async def async_attach_trigger(
device_type = _device_model_to_type(device.model) device_type = _device_model_to_type(device.model)
_, serial = list(device.identifiers)[0] _, serial = list(device.identifiers)[0]
schema = DEVICE_TYPE_SCHEMA_MAP.get(device_type) schema = DEVICE_TYPE_SCHEMA_MAP.get(device_type)
valid_buttons = DEVICE_TYPE_SUBTYPE_MAP.get(device_type) valid_buttons = DEVICE_TYPE_SUBTYPE_MAP_TO_LIP.get(device_type)
config = schema(config) config = schema(config)
event_config = { event_config = {
event_trigger.CONF_PLATFORM: CONF_EVENT, event_trigger.CONF_PLATFORM: CONF_EVENT,

View File

@ -2,7 +2,7 @@
"domain": "lutron_caseta", "domain": "lutron_caseta",
"name": "Lutron Cas\u00e9ta", "name": "Lutron Cas\u00e9ta",
"documentation": "https://www.home-assistant.io/integrations/lutron_caseta", "documentation": "https://www.home-assistant.io/integrations/lutron_caseta",
"requirements": ["pylutron-caseta==0.11.0", "aiolip==1.1.6"], "requirements": ["pylutron-caseta==0.13.0"],
"config_flow": true, "config_flow": true,
"zeroconf": ["_leap._tcp.local."], "zeroconf": ["_leap._tcp.local."],
"homekit": { "homekit": {

View File

@ -209,9 +209,6 @@ aiolifx==0.7.0
# homeassistant.components.lifx # homeassistant.components.lifx
aiolifx_effects==0.2.2 aiolifx_effects==0.2.2
# homeassistant.components.lutron_caseta
aiolip==1.1.6
# homeassistant.components.lookin # homeassistant.components.lookin
aiolookin==0.1.0 aiolookin==0.1.0
@ -1625,7 +1622,7 @@ pylitejet==0.3.0
pylitterbot==2021.12.0 pylitterbot==2021.12.0
# homeassistant.components.lutron_caseta # homeassistant.components.lutron_caseta
pylutron-caseta==0.11.0 pylutron-caseta==0.13.0
# homeassistant.components.lutron # homeassistant.components.lutron
pylutron==0.2.8 pylutron==0.2.8

View File

@ -142,9 +142,6 @@ aiohue==3.0.6
# homeassistant.components.apache_kafka # homeassistant.components.apache_kafka
aiokafka==0.6.0 aiokafka==0.6.0
# homeassistant.components.lutron_caseta
aiolip==1.1.6
# homeassistant.components.lookin # homeassistant.components.lookin
aiolookin==0.1.0 aiolookin==0.1.0
@ -995,7 +992,7 @@ pylitejet==0.3.0
pylitterbot==2021.12.0 pylitterbot==2021.12.0
# homeassistant.components.lutron_caseta # homeassistant.components.lutron_caseta
pylutron-caseta==0.11.0 pylutron-caseta==0.13.0
# homeassistant.components.mailgun # homeassistant.components.mailgun
pymailgunner==1.4 pymailgunner==1.4