Effects Troubleshooting¶
This guide helps you diagnose and resolve common issues when using the Light Effects Framework.
Table of Contents¶
- Common Issues
- Device Compatibility
- Performance Issues
- State Management
- Debugging Techniques
- Known Limitations
Common Issues¶
Effects Don't Start¶
Symptom: Calling conductor.start() doesn't appear to do anything.
Possible Causes:
- No await keyword
# Wrong - missing await
conductor.start(effect, lights) # Returns immediately, nothing happens
# Correct
await conductor.start(effect, lights)
- Devices not reachable
# Check device connectivity first
from lifx import discover, DeviceGroup
devices = []
async for device in discover():
devices.append(device)
group = DeviceGroup(devices)
if not group.lights:
print("No devices found!")
return
# Now safe to use effects
conductor = Conductor()
await conductor.start(effect, group.lights)
- Empty participants list
# Check you have lights
if not lights:
print("No lights to apply effect to")
return
await conductor.start(effect, lights)
Solution: Always use await and verify devices are discovered before starting effects.
Lights Don't Restore to Original State¶
Symptom: After effect completes, lights stay in effect state instead of returning to original.
Possible Causes:
- Missing conductor.stop() call
# ColorLoop requires manual stop
effect = EffectColorloop(period=30)
await conductor.start(effect, lights)
await asyncio.sleep(60)
# MISSING: await conductor.stop(lights)
Solution: Always call conductor.stop() for continuous effects:
- Effect doesn't call conductor.stop() internally
Custom effects must restore state:
async def async_play(self) -> None:
# Effect logic
...
# Required for auto-restore
if self.conductor:
await self.conductor.stop(self.participants)
- Network timeout during restoration
If restoration fails due to network issues, lights may be in inconsistent state.
Solution: Check logs for timeout errors, verify network connectivity.
Effect Appears to Freeze¶
Symptom: Effect starts but never completes, script hangs.
Possible Causes:
- ColorLoop running indefinitely
ColorLoop is designed to run forever:
# This will hang forever
effect = EffectColorloop(period=30)
await conductor.start(effect, lights)
# Script hangs here - ColorLoop never completes
Solution: Call conductor.stop() explicitly:
effect = EffectColorloop(period=30)
await conductor.start(effect, lights)
await asyncio.sleep(60) # Let it run
await conductor.stop(lights) # Stop it
- Custom effect with infinite loop
Solution: Add stop condition:
async def async_play(self) -> None:
self._running = True
while self._running:
await self._do_something()
- Missing await in effect logic
async def async_play(self) -> None:
# Missing await - blocks event loop
light.set_color(color) # Should be: await light.set_color(color)
Solution: Always use await on async operations.
Lights Flash/Reset Between Effects¶
Symptom: When starting second effect, lights briefly return to original state before new effect starts.
Cause: State inheritance not enabled.
# Each effect resets to original state
effect1 = EffectColorloop(period=30)
await conductor.start(effect1, lights)
await asyncio.sleep(10)
effect2 = EffectColorloop(period=20) # Lights briefly reset here
await conductor.start(effect2, lights)
Solution: Effects must implement inherit_prestate() to prevent reset:
class EffectColorloop(LIFXEffect):
def inherit_prestate(self, other: LIFXEffect) -> bool:
return isinstance(other, EffectColorloop)
This is already implemented for EffectColorloop, but custom effects may need it.
Note: For different effect types, the reset is intentional behavior.
Pulse Effect Too Fast/Slow¶
Symptom: Pulse timing doesn't match expectations.
Cause: Misunderstanding period vs. total duration.
# This runs for 1 second total (period=1.0, cycles=1)
effect = EffectPulse(mode='blink', period=1.0, cycles=1)
# This runs for 5 seconds total (period=1.0, cycles=5)
effect = EffectPulse(mode='blink', period=1.0, cycles=5)
# This runs for 2 seconds total (period=2.0, cycles=1)
effect = EffectPulse(mode='blink', period=2.0, cycles=1)
Solution: Total duration = period * cycles
ColorLoop Colors Look Wrong¶
Symptom: ColorLoop shows unexpected colors or is too dim/bright.
Possible Causes:
- Saturation constraints too restrictive
# Very low saturation = washed out colors
effect = EffectColorloop(saturation_min=0.1, saturation_max=0.3) # Pastels
Solution: Use higher saturation for vibrant colors:
- Brightness locked to low value
Solution: Use higher brightness or None to preserve original:
effect = EffectColorloop(brightness=None) # Preserve original
# or
effect = EffectColorloop(brightness=0.8) # 80% brightness
- Monochrome device
ColorLoop doesn't work on monochrome/white-only lights.
Solution: Only use ColorLoop on color-capable devices.
Multizone Lights Don't Restore Zones Correctly¶
Symptom: After effect, multizone light zones are wrong color or all same color.
Possible Causes:
- Device was powered off before effect
Some older multizone devices report all zones as the same color when powered off.
Workaround: Ensure lights are powered on before starting effects:
# Power on first
for light in lights:
await light.set_power(True)
await asyncio.sleep(0.5)
# Now start effect
await conductor.start(effect, lights)
- Extended multizone messages not supported
Older devices may not support efficient extended multizone messages.
Solution: Framework automatically falls back to standard messages. No action needed.
- Network timeouts during zone restoration
If restoring many zones times out, state may be incomplete.
Solution: Check network stability, reduce concurrent operations.
Device Compatibility¶
Color Lights¶
Full Support: All effects work as expected.
Models: LIFX Color, LIFX+, LIFX Mini Color, LIFX Candle Color
Monochrome/White Lights¶
Limited Support: Only brightness-based effects work.
What Works:
- EffectPulse: Brightness pulsing (hue/saturation ignored)
- Custom effects using only brightness
What Doesn't Work:
- EffectColorloop: No visible effect (can't change hue)
- Color-based custom effects: Only brightness changes visible
Recommendation: Avoid ColorLoop on monochrome devices.
Models: LIFX White, LIFX Mini White, LIFX Downlight
Multizone Lights¶
Full Support with some considerations.
Works Well:
- EffectPulse: All zones pulse together
- EffectColorloop: Entire device cycles color
Special Considerations:
- Effects apply to entire device, not individual zones
- Zone colors properly restored after effect
- Extended multizone messages used when available
Potential Enhancement: Future versions could support per-zone effects.
Models: LIFX Z, LIFX Beam
Matrix Lights (Tile/Candle/Path)¶
Full Support (treated as single unit).
Works Well:
- EffectPulse: All tiles/zones pulse together
- EffectColorloop: All tiles/zones cycle color together
Limitation: Current implementation doesn't use per-tile control. All tiles show same color.
Potential Enhancement: Future versions could support per-tile effects (similar to theme support).
Models: LIFX Tile, LIFX Candle, LIFX Path
HEV Lights¶
Full Support (treated like standard color lights).
Note: Effects don't interfere with HEV cycle functionality.
Models: LIFX Clean
Infrared Lights¶
Full Support (treated like standard color lights).
Note: Effects control visible light only, infrared LED not affected.
Models: LIFX+, LIFX Night Vision
Performance Issues¶
Slow Effect Startup¶
Symptom: Noticeable delay before effect starts.
Cause: State capture requires network round trips.
Expected Timing:
- Single device: <1 second
- 10 devices: <1 second (concurrent)
- 50 devices: 1-2 seconds
If Slower:
- Check network latency (ping devices)
- Verify devices are on local network (not remote)
- Reduce concurrent discovery operations
Choppy/Stuttering Effects¶
Symptom: Effects don't run smoothly, visible stuttering.
Possible Causes:
- Too many concurrent effects
# 50 devices all running independent effects
for light in lights:
await conductor.start(effect, [light]) # Too many!
Solution: Group devices:
- Network congestion
Too many packets sent too quickly can overwhelm network or devices.
Solution: Add rate limiting:
# In custom effect
for iteration in range(self.iterations):
await self._update_colors()
await asyncio.sleep(0.05) # Rate limit: max 20/sec
- Blocking operations in effect
# Bad - blocking sleep
import time
time.sleep(1) # Blocks entire event loop!
# Good - async sleep
await asyncio.sleep(1)
Solution: Always use async operations.
Effects on Many Devices Are Slow¶
Symptom: Effects take much longer with many devices.
Expected Behavior: Effects should scale linearly (not exponentially).
If Slower Than Expected:
- Verify concurrent operations are used:
# Good - concurrent
await asyncio.gather(*[
light.set_color(color) for light in lights
])
# Bad - sequential
for light in lights:
await light.set_color(color)
-
Check for sequential operations in custom effects
-
Verify network capacity isn't saturated
Recommendation: For 50+ devices, consider:
- Staggering effect starts
- Using fewer concurrent effects
- Implementing application-level rate limiting
State Management¶
State Captured Incorrectly¶
Symptom: Restored state doesn't match original state.
Possible Causes:
- State changed between capture and effect
# State captured here
await conductor.start(effect, lights)
# Meanwhile, user changes light with app
# Effect completes, restores OLD state (not current state)
Solution: Effects framework works correctly - this is expected behavior. State is captured at effect start.
- Multizone device powered off during capture
Older devices report inaccurate zone colors when off.
Workaround: Power on before effect:
for light in lights:
await light.set_power(True)
await asyncio.sleep(0.5)
await conductor.start(effect, lights)
State Restoration Fails Silently¶
Symptom: State restoration errors not visible.
Cause: Errors are logged but don't raise exceptions (by design - one failed device shouldn't stop others).
Solution: Enable debug logging:
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('lifx.effects')
logger.setLevel(logging.DEBUG)
Check logs for warnings like:
Debugging Techniques¶
Enable Debug Logging¶
See detailed information about effect execution:
import logging
# Enable debug logging for effects
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Just effects module
logger = logging.getLogger('lifx.effects')
logger.setLevel(logging.DEBUG)
Output shows:
- State capture details
- Prestate inheritance decisions
- State restoration steps
- Error messages
Check Current Effect Status¶
See what's currently running on each device:
conductor = Conductor()
# After starting effects
for light in lights:
current = conductor.effect(light)
if current:
print(f"{light.label}: {type(current).__name__}")
else:
print(f"{light.label}: idle")
Verify Device Connectivity¶
Before effects, verify all devices are reachable:
async def check_connectivity(lights):
"""Verify all lights respond."""
for light in lights:
try:
label = await light.get_label()
print(f"✓ {label} reachable")
except Exception as e:
print(f"✗ {light.serial} unreachable: {e}")
# Use before effects
from lifx import discover, DeviceGroup
devices = []
async for device in discover():
devices.append(device)
group = DeviceGroup(devices)
await check_connectivity(group.lights)
Test with Single Device First¶
Isolate issues by testing with one device:
# Test with single device first
from lifx import discover, DeviceGroup
devices = []
async for device in discover():
devices.append(device)
group = DeviceGroup(devices)
if group.lights:
test_light = group.lights[0]
conductor = Conductor()
effect = EffectPulse(mode='blink', cycles=3)
print(f"Testing with {await test_light.get_label()}")
await conductor.start(effect, [test_light])
await asyncio.sleep(4)
print("Test complete - check if light restored correctly")
Validate Effect Parameters¶
Check that effect parameters are valid:
# Add parameter validation
class MyEffect(LIFXEffect):
def __init__(self, count: int, period: float, power_on: bool = True):
super().__init__(power_on=power_on)
if count < 1:
raise ValueError(f"count must be positive, got {count}")
if period <= 0:
raise ValueError(f"period must be positive, got {period}")
self.count = count
self.period = period
Measure Effect Timing¶
Verify effect runs for expected duration:
import time
start = time.time()
effect = EffectPulse(mode='blink', period=1.0, cycles=5)
await conductor.start(effect, lights)
# Expected: 5 seconds
await asyncio.sleep(6)
elapsed = time.time() - start
print(f"Effect took {elapsed:.1f}s (expected ~5s)")
Known Limitations¶
Rate Limiting¶
The effects framework does not implement automatic rate limiting.
Impact: Sending too many concurrent commands may overwhelm devices or network.
LIFX Limit: ~20 messages per second per device
Recommendation: For rapid-fire effects, add your own rate limiting:
async def async_play(self) -> None:
for i in range(100):
await self._update_lights()
await asyncio.sleep(0.05) # 20/sec max
Tile Per-Tile Effects¶
Current implementation treats tiles as a single unit.
Limitation: Can't apply different effects to individual tiles within a tile chain.
Workaround: Use theme support for per-tile colors, or wait for future enhancement.
Potential Future: Per-tile effect logic could be added using MatrixLight.set_matrix_colors().
Multizone Per-Zone Effects¶
Current implementation treats multizone device as a single unit.
Limitation: Can't pulse individual zones or create zone-specific effects.
Workaround: Manually use set_color_zones() in custom effects.
Example:
from lifx import MultiZoneLight
async def async_play(self) -> None:
for light in self.participants:
if isinstance(light, MultiZoneLight):
# Control individual zones
zone_count = await light.get_zone_count()
for i in range(zone_count):
color = self._get_zone_color(i)
await light.set_color_zones(i, i, color)
Button/Relay/Switch Devices¶
The effects framework only supports lighting devices.
Not Supported:
- LIFX Switch
- LIFX Relay
- Button devices
Reason: Effects are designed for visual output (lights), not control devices.
Network Timeouts with Many Devices¶
With 50+ devices, state capture/restoration may timeout.
Symptoms:
- Some devices don't restore state
- Timeout errors in logs
Solutions:
- Increase timeout values (requires lifx-async modification)
- Reduce number of concurrent effects
- Group devices and stagger effect starts
- Verify network infrastructure can handle traffic
Prestate Inheritance Limitations¶
State inheritance is conservative to prevent artifacts.
Current Behavior:
- Only
EffectColorloopsupports inheritance (from otherEffectColorloop) - Other effect types always reset state
Enhancement Opportunity: More effect types could support inheritance with careful design.
Still Having Issues?¶
If you're experiencing issues not covered here:
- Check the logs with debug logging enabled
- Test with single device to isolate the problem
- Verify device firmware is up to date
- Check network connectivity and stability
- Review examples in the
examples/directory - Report issues on GitHub Issues
When reporting issues, include:
- lifx-async version
- Python version
- Device model(s) affected
- Minimal reproduction code
- Full error message and traceback
- Debug logs if applicable
See Also¶
- Getting Started - Basic usage patterns
- Effects Reference - Detailed API documentation
- Custom Effects - Creating your own effects
- Architecture - How the system works