Pico Link turns any Lutron Casรฉta Pico Remote into a native, domain-aware Home Assistant controller.
It listens directly to:
lutron_caseta_button_event
and provides:
- Tap vs hold detection
- Step vs ramp behavior
- Domain-specific logic (lights, fans, covers, media players, switches)
- STOP button semantics (3BRL)
- Scene execution (4-button Picos)
- Placeholder expansion for device-scoped actions
| Pico Type | Layout | Buttons | Hold Supported | Typical Use | Notes |
|---|---|---|---|---|---|
| P2B | Paddle | on, off |
โ | Lights, fans, covers, media | Raise/lower inferred |
| 2B | On/Off | on, off |
โ | Lights, fans, covers, media | Raise/lower inferred |
| 3BRL | On / Raise / Stop /Lower / Off | on, raise, stop, lower, off |
โ | Full device control | STOP is domain-aware |
| 4B | 4 Scenes | button_1โฆ3, off |
โ | Scenes / scripts | No domain logic |
-
HACS โ Integrations
-
โฎ โ Custom Repositories
-
Add:
https://github.com/smartqasa/pico-link -
Type: Integration
-
Install Pico Link
-
Restart Home Assistant
Copy into:
config/custom_components/pico_link/
Restart Home Assistant.
pico_link:
defaults: # optional
devices: # requiredEach device entry must define:
typenameordevice_id- One domain (except 4B)
Valid domains:
covers:
fans:
lights:
media_players:
switches:
4-button Picos use buttons: instead of a domain.
Pico Link relies on time-based heuristics to distinguish:
- Tap vs hold
- Step vs ramp
These values are behavior-critical, not cosmetic.
If you are unsure, do not override timing defaults. Poor values can cause missed taps, runaway ramps, or unexpected STOP behavior.
| Parameter | Default | Purpose |
|---|---|---|
hold_time_ms |
400 |
Tap โ Hold threshold |
step_time_ms |
650 |
Ramp repeat interval |
hold_time_ms: 300โ500
step_time_ms: 600โ1000
Values outside these ranges are discouraged.
| Key | Required | Default | Description |
|---|---|---|---|
type |
โ | โ | Pico hardware type |
name / device_id |
โ | โ | Pico identity |
Domain (lights, etc.) |
โ (4B โ) | โ | Controlled entities |
buttons |
4B only | {} |
Scene/action mappings |
middle_button |
3BRL only | domain default | STOP behavior |
hold_time_ms |
โ | 400 |
Hold threshold (ms) |
step_time_ms |
โ | 650 |
Ramp interval (ms) |
cover_open_pos |
โ | 100 |
ON open position (pct) |
cover_step_pct |
โ | 10 |
Cover step (pct) |
cover_inverted |
โ | false | Reverses Open/Close btns |
fan_on_pct |
โ | 100 |
ON speed (pct) |
light_on_pct |
โ | 100 |
ON brightness (pct) |
light_low_pct |
โ | 5 |
Minimum dim (pct) |
light_step_pct |
โ | 10 |
Step size (pct) |
light_transition_on |
โ | 0 |
Fade-in time (seconds) |
light_transition_off |
โ | 0 |
Fade-out time (seconds) |
media_player_vol_step |
โ | 10 |
Volume step (pct) |
light_transition_on and light_transition_off control smooth fades when
lights are turned ON or OFF via tap actions.
- Value is in seconds
- When set to
0, the transition parameter is not sent to Home Assistant - Transitions apply to tap ON / OFF only
- Step and ramp actions are always instant for responsiveness
STOP behavior is explicit and deterministic.
- Device
middle_button - Global default
middle_button(if set todefault) - Domain default behavior
middle_button: [] # use domain default
middle_button: default # use global default
middle_button: # explicit custom actions
- action: ...| Domain | STOP Behavior |
|---|---|
| Covers | Stop Cover |
| Fans | Reverse direction |
| Lights | No-op |
| Media Players | Toggle mute |
| Switches | No-op |
Within middle_button: actions, these placeholders expand automatically:
| Placeholder | Expands To |
|---|---|
covers |
Assigned covers |
fans |
Assigned fans |
lights |
Assigned lights |
media_players |
Assigned media players |
switches |
Assigned switches |
Example:
target:
entity_id:
- lights
- light.accent_lamp| Button | Action |
|---|---|
| ON | turn_on โ light_on_pct |
| OFF | turn_off |
| RAISE | step / ramp up |
| LOWER | step / ramp down |
| STOP | no-op |
| Button | Action |
|---|---|
| ON | set_percentage โ fan_on_pct |
| OFF | turn_off |
| RAISE | step / ramp up |
| LOWER | step / ramp down |
| STOP | reverse_direction |
| Button | Action |
|---|---|
| ON | open โ cover_open_pos |
| OFF | close |
| RAISE | step / ramp open |
| LOWER | step / ramp close |
| STOP | stop_cover |
| Button | Action |
|---|---|
| ON | play / play_pause |
| OFF | next_track |
| RAISE | volume up |
| LOWER | volume down |
| STOP | mute / unmute |
| Button | Action |
|---|---|
| ON | turn_on |
| OFF | turn_off |
| Others | no-op |
pico_link:
defaults:
# Optional global STOP behavior (3BRL)
middle_button:
- action: light.turn_on
target:
entity_id: lights
data:
brightness_pct: 80
transition: 1
devices:
# P2B Paddle โ lights
- name: Kitchen Paddle
type: P2B
lights:
- light.kitchen_main
light_transition_on: 1
light_transition_off: 3
# 2B โ switch
- name: Closet Pico
type: 2B
switches:
- switch.closet_light
# 3BRL โ lights, uses global STOP
- name: Bedroom Remote
type: 3BRL
lights:
- light.bedroom_main
- light.bedroom_lamps
middle_button: default
# 3BRL โ fan, custom STOP
- name: Living Room Fan
type: 3BRL
fans:
- fan.living_room
fan_on_pct: 40
# 3BRL โ cover, domain STOP
- name: Shade Remote
type: 3BRL
covers:
- cover.living_room_shade
# 3BRL โ media player
- name: Office Media
type: 3BRL
media_players:
- media_player.office_sonos
media_player_vol_step: 5
# 4B โ scenes
- name: Scene Pico
type: 4B
buttons:
button_1:
- action: scene.turn_on
target:
entity_id: scene.movie
button_2:
- action: script.dim_lights
button_3:
- action: light.turn_off
target:
entity_id:
- light.kitchen
- light.living_room
off:
- action: homeassistant.turn_off
target:
area_id: main_floorPico Link enforces:
- One domain per device (except 4B)
- Valid Pico types
- Correct STOP usage
- Valid numeric ranges
- Clear, actionable config errors
