Add pico remote support to non-pro lutron caseta bridges (#61032)
parent
832184bacd
commit
e834382b9a
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue