Pico Click: Double & Long Press for All Lutron Pico Remotes (Custom Integration)
I built a custom integration that adds single, double, and long press detection to all Lutron Caseta Pico remotes. No per-remote configuration, no blueprints, no replacing the core integration.
The Problem
The core Lutron Caseta integration only gives you raw press and release events for Pico remotes. If you want double-press or long-press actions, the existing solutions require either:
- A blueprint per remote (with separate automations for each Pico)
- A fork that replaces the core Caseta integration files
- Double-press on raise/lower buttons is often disabled as “bad experience”
The Solution
pico_click is a standalone custom component that listens for raw lutron_caseta_button_event press/release events and fires clean pico_button_click events with click_type: single, double, or long.
Install it once, configure your timing preferences globally, and every Pico in your system gets double/long press support automatically. It sits alongside the core Caseta integration and doesn’t modify or replace anything.
How It Works
The component runs a state machine for each button:
- On press: if a timer is already running (previous press) → double click. Otherwise, start a timer.
- On release: if a long press already fired → consume the release. Otherwise, mark the release.
- On timer expire: if no release received → long press. If released → single press.
Repeat Behavior for Raise/Lower
Buttons in the slow_buttons list (typically raise and lower) get special handling:
- Extra time window for double-click detection (
slow_extraparameter) - Holding fires repeated
longevents at configurable intervals, perfect for stepping fan speed or brightness - All other buttons fire long press exactly once
Configuration
# configuration.yaml
pico_click:
click_time: 0.5 # seconds, window to detect double click
slow_extra: 0.25 # extra time for slow buttons (raise/lower)
repeat_time: 0.5 # interval for long-press repeat
repeat_max: 10 # max repeat count
slow_buttons: # buttons that get extra time AND repeat on long press
- raise
- lower
That’s it. No per-remote configuration. Every Pico on your Caseta bridge is covered.
Event Format
event_type: pico_button_click
data:
device_id: 4db3aae61480b353222838233e4b8f1b
device_name: Master Bedroom Pico
area_name: Master Bedroom
serial: 43195928
type: Pico3ButtonRaiseLower
button_number: 3
button_type: stop
click_type: double # single, double, or long
Example Automation
One Pico controlling two devices. Single press for the ceiling fan, double press for a Dyson fan:
- id: master_bedroom_fan_control
alias: Master Bedroom Fan Control
trigger:
- platform: event
event_type: pico_button_click
event_data:
device_id: 4db3aae61480b353222838233e4b8f1b
action:
- variables:
button: "{{ trigger.event.data.button_type }}"
click: "{{ trigger.event.data.click_type }}"
- choose:
# Single press - ceiling fan
- conditions: "{{ button == 'on' and click == 'single' }}"
sequence:
- service: fan.turn_on
target:
entity_id: fan.ceiling_fan
- conditions: "{{ button == 'off' and click == 'single' }}"
sequence:
- service: fan.turn_off
target:
entity_id: fan.ceiling_fan
- conditions: "{{ button == 'raise' and click in ['single', 'long'] }}"
sequence:
- service: fan.increase_speed
target:
entity_id: fan.ceiling_fan
- conditions: "{{ button == 'lower' and click in ['single', 'long'] }}"
sequence:
- service: fan.decrease_speed
target:
entity_id: fan.ceiling_fan
# Double press - Dyson fan
- conditions: "{{ button == 'on' and click == 'double' }}"
sequence:
- service: fan.turn_on
target:
entity_id: fan.dyson_am07
- conditions: "{{ button == 'off' and click == 'double' }}"
sequence:
- service: fan.turn_off
target:
entity_id: fan.dyson_am07
Comparison with Existing Solutions
| Blueprint | lutron-caseta-pro fork | pico_click | |
|---|---|---|---|
| Per-remote config | Yes | Yes | No |
| Replaces core integration | No | Yes | No |
| Raise/lower double-press | Disabled | Supported | Supported with tunable timing |
| Long-press repeat | No | No | Yes, configurable |
| Event-based | No (inline actions) | Custom codes | Clean typed events |
Installation
- Create the directory
/config/custom_components/pico_click/ - Create the two files below
- Add the configuration block to
configuration.yaml - Restart Home Assistant
/config/custom_components/pico_click/manifest.json
{
"domain": "pico_click",
"name": "Pico Click",
"version": "1.0.0",
"documentation": "https://github.com/rnilssoncx/homebridge-pico",
"dependencies": [],
"codeowners": [],
"iot_class": "local_push"
}
/config/custom_components/pico_click/__init__.py
"""Pico Click - Single, double, and long press detection for Lutron Pico remotes.
Ported from homebridge-pico by Robert Nilsson.
Listens for raw lutron_caseta_button_event press/release events and fires
pico_button_click events with click_type: single, double, or long.
"""
import asyncio
import logging
from homeassistant.core import HomeAssistant, Event
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
DOMAIN = "pico_click"
EVENT_PICO_CLICK = "pico_button_click"
EVENT_CASETA = "lutron_caseta_button_event"
DEFAULT_CLICK_TIME = 0.5
DEFAULT_SLOW_EXTRA = 0.25
DEFAULT_REPEAT_TIME = 0.5
DEFAULT_REPEAT_MAX = 10
DEFAULT_SLOW_BUTTONS = ["raise", "lower"]
class ClickTracker:
"""State machine for detecting single, double, and long press.
Ported from the Click class in homebridge-pico index.js.
Logic:
- On press: if timer running -> double click, else start timer
- On release: if long press already fired, stop everything
- On timer expire: if no release -> long press, else -> single press
- Repeat only fires for buttons in the repeat list (raise/lower)
"""
def __init__(self, hass, event_data, click_time, repeat_time, repeat_max, repeat, callback):
self._hass = hass
self._device_id = event_data["device_id"]
self._button_type = event_data["button_type"]
self._event_data = event_data
self._click_time = click_time
self._repeat_time = repeat_time
self._repeat_max = repeat_max
self._repeat = repeat
self._callback = callback
self._timer = None
self._ups = 0
self._repeat_count = 0
self._long_fired = False
self._released = False
async def click(self, action):
if action == "press":
self._long_fired = False
self._released = False
if self._timer is not None:
await self._finished(double_click=True)
else:
self._set_timer(self._click_time)
elif action == "release":
self._released = True
if self._long_fired:
if self._timer is not None:
self._timer.cancel()
self._timer = None
self._repeat_count = 0
return
if self._timer is not None:
self._ups += 1
def _set_timer(self, seconds):
if self._timer is not None:
self._timer.cancel()
self._timer = self._hass.loop.call_later(
seconds, lambda: asyncio.ensure_future(self._finished())
)
self._ups = 0
async def _finished(self, double_click=False):
if self._timer is not None:
self._timer.cancel()
self._timer = None
if self._released and self._long_fired:
self._repeat_count = 0
return
if double_click:
click_type = "double"
self._repeat_count = 0
elif self._ups == 0:
click_type = "long"
self._long_fired = True
if self._repeat:
self._repeat_count += 1
if self._repeat_count < self._repeat_max and not self._released:
self._set_timer(self._repeat_time)
else:
self._repeat_count = 0
else:
click_type = "single"
self._repeat_count = 0
if self._repeat_count == 0 or click_type == "long":
await self._callback(self._event_data, click_type)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Pico Click component."""
conf = config.get(DOMAIN, {})
click_time = conf.get("click_time", DEFAULT_CLICK_TIME)
slow_extra = conf.get("slow_extra", DEFAULT_SLOW_EXTRA)
repeat_time = conf.get("repeat_time", DEFAULT_REPEAT_TIME)
repeat_max = conf.get("repeat_max", DEFAULT_REPEAT_MAX)
slow_buttons = conf.get("slow_buttons", DEFAULT_SLOW_BUTTONS)
trackers = {}
async def fire_click(event_data, click_type):
"""Fire a pico_button_click event."""
data = {
"device_id": event_data.get("device_id", ""),
"device_name": event_data.get("device_name", ""),
"area_name": event_data.get("area_name", ""),
"serial": event_data.get("serial", ""),
"type": event_data.get("type", ""),
"button_number": event_data.get("button_number", ""),
"button_type": event_data.get("button_type", ""),
"click_type": click_type,
}
_LOGGER.debug("Pico click: %s %s %s", data["device_name"], data["button_type"], click_type)
hass.bus.async_fire(EVENT_PICO_CLICK, data)
async def handle_caseta_event(event: Event):
"""Handle raw Caseta button events."""
data = event.data
action = data.get("action")
if action not in ("press", "release"):
return
device_id = data.get("device_id", "")
button_type = data.get("button_type", "")
key = f"{device_id}_{button_type}"
if key not in trackers:
button_click_time = click_time
repeat = button_type in slow_buttons
if repeat:
button_click_time += slow_extra
trackers[key] = ClickTracker(
hass, data, button_click_time, repeat_time, repeat_max, repeat, fire_click
)
else:
trackers[key]._event_data = data
await trackers[key].click(action)
hass.bus.async_listen(EVENT_CASETA, handle_caseta_event)
_LOGGER.info(
"Pico Click loaded (click_time=%.2fs, slow_extra=%.2fs, repeat_time=%.2fs, repeat_buttons=%s)",
click_time, slow_extra, repeat_time, slow_buttons,
)
return True
Background
I originally built homebridge-pico, a Homebridge plugin that added double and long press to Pico remotes via the Caseta Telnet protocol. It’s been running reliably in my setup for years and I wanted the same simplicity and reliability when I moved to Home Assistant.
The existing HA solutions (per-remote blueprints and integration forks) felt like a step backwards. I wanted what homebridge-pico gave me: install once, configure timing globally, and every Pico just works. pico_click brings that same approach to HA as a lightweight custom component that sits alongside the core Caseta integration.
Key adaptation from Homebridge: The original plugin used Caseta Telnet protocol action codes (3=down, 4=up). HA’s Caseta integration sends action: press and action: release with string button_type values (on, off, raise, lower, stop) instead of numeric button IDs. The state machine logic is the same, only the event interface is different.
To find the correct button_type for any Pico, look at Settings > Devices & Services > Lutron Caseta > [device] > Controls. The button entity names match the button_type values in events.