Trying to get the most I can out of these old switches. They don’t provide enough information in ZHA to do reasonable dimmer control, but Z2MQTT provides a little more info for control.
# Zigbee2MQTT - Lutron Aurora Control
# - Listens to MQTT messages on zigbee2mqtt/<device>
# - Maps switch device names to target light entities using SWITCH_TO_LIGHTS dictionary
# - Button press toggles lights on/off
# - Rotation adjusts brightness (only when lights are already on)
# - Uses action_level and action_transition_time from Z2M payloads
# logger.set_level(**{"custom_components.pyscript.file.zigbee2mqtt_lutron_aurora_control": "debug"})
# Configuration: Map switch device names to target light entities
# Key: Switch name (from MQTT)
# Value: List of light entity IDs that this switch controls
SWITCH_TO_LIGHTS = {
"Office Hue Switch": [
"light.office_nano_bulb_01",
"light.office_nano_bulb_02",
"light.office_hue_lightstrip",
],
"Living Room Hue Switch": ["light.magic_areas_light_groups_living_room_all_lights"],
}
# Brightness settings
BRIGHTNESS_MIN = 10
BRIGHTNESS_MAX = 255
DEFAULT_TRANSITION = 0.2
# Clamp brightness to valid range
def clamp_brightness(value: int) -> int:
return max(BRIGHTNESS_MIN, min(value, BRIGHTNESS_MAX))
# Returns brightness of first lit entity, or None if no lights are on
def get_current_brightness(target_lights: list[str]) -> int | None:
for entity in target_lights:
if state.get(entity) == "on":
brightness = state.get(f"{entity}.brightness")
if brightness is not None:
return int(brightness)
return None
# Check if the event is a button press (vs rotation)
def is_press_event(transition_time: float, action_level: int) -> bool:
# Aurora sends transition_time=0.07 for button presses
if abs(transition_time - 0.07) < 1e-3:
return True
return False
@mqtt_trigger("zigbee2mqtt/+")
def on_z2m_event(topic=None, payload=None, payload_obj=None, **kwargs):
if not topic or not topic.startswith("zigbee2mqtt/"):
return
device_name = topic.split("/", 1)[1]
# Look up target lights from configuration mapping
target_lights = SWITCH_TO_LIGHTS.get(device_name)
if target_lights is None:
log.debug(f"No mapping found for switch '{device_name}', skipping")
return
log.debug(f"TARGETING: {target_lights}")
# Check if any light entity exists
entity_exists = False
for entity in target_lights:
if state.get(entity):
entity_exists = True
break
if not entity_exists:
log.debug(f"No light entities found in {target_lights}, skipping")
return
# Parse payload
action_level = (payload_obj or {}).get("action_level")
transition_time = (payload_obj or {}).get("action_transition_time", 0)
# Ignore if no action_level reported
if action_level is None:
return
# Handle button press: toggle light
if is_press_event(transition_time, action_level):
log.debug(f"{device_name} --> TOGGLE")
light.toggle(entity_id=target_lights)
return
# Handle rotation: adjust brightness (only when lights are on)
current_brightness = get_current_brightness(target_lights)
if current_brightness is None:
return
new_brightness = clamp_brightness(action_level)
# Skip redundant write if no change
if current_brightness == new_brightness:
return
log.debug(f"{device_name} --> Rotate dim -> brightness {new_brightness}")
light.turn_on(
entity_id=target_lights,
brightness=new_brightness,
transition=DEFAULT_TRANSITION,
)