LED-Aware Effects Migration Guide
This guide helps developers migrate from the previous effects system to the new LED-aware effects system introduced in BusyLight.
Overview of Changes
The LED-aware effects system allows targeting specific LEDs on multi-LED devices (like Blink1 mk2) while maintaining full backward compatibility with existing code.
Key Improvements
- LED Targeting: All effects can now target specific LEDs using the
led
parameter - Unified System: Removed special-case LED blink implementation - all effects use the same system
- API Consistency: LED parameter is now available across CLI, Web API, and Python API
- Backward Compatible: Existing code continues to work without changes
Migration Scenarios
1. Basic Effect Usage (No Changes Required)
Before and After - Identical:
# These work exactly the same as before
controller.all().turn_on("red")
controller.all().blink("blue", count=5)
controller.all().apply_effect(Spectrum())
# CLI commands unchanged
busylight on red
busylight blink blue --count 5
busylight rainbow
2. LED-Specific Operations (New Capability)
Before: LED-specific operations were limited to basic on/blink
# Old: Only basic operations supported LED parameter
controller.all().turn_on("red", led=1)
controller.all().blink("blue", led=2, count=3)
# Effects couldn't target specific LEDs
controller.all().apply_effect(Spectrum()) # Always all LEDs
After: All effects support LED targeting
# New: All operations and effects support LED parameter
controller.all().turn_on("red", led=1)
controller.all().blink("blue", led=2, count=3)
# Effects can now target specific LEDs
controller.all().apply_effect(Spectrum(), led=1)
controller.all().apply_effect(Gradient((255, 0, 0)), led=2)
3. CLI Command Enhancements
Before: Limited LED support
# Old: Only basic commands had LED support
busylight on red --led 1
busylight blink blue --led 2 --count 3
# Effects lacked LED support
busylight rainbow # Always all LEDs
busylight pulse red # Always all LEDs
After: All effects support LED targeting
# Enhanced: All commands support LED targeting
busylight on red --led 1
busylight blink blue --led 2 --count 3
# Effects now support LED targeting
busylight rainbow --led 1
busylight pulse red --led 2 --count 5
busylight fli red blue --led 1 --count 3
4. Web API Enhancements
Before: Limited LED endpoints
# Old: Only basic operations had LED support
curl "http://localhost:8000/lights/on?color=red&led=1"
curl "http://localhost:8000/lights/blink?color=blue&led=2"
# Effects lacked LED support
curl "http://localhost:8000/lights/rainbow" # Always all LEDs
curl "http://localhost:8000/lights/pulse?color=red" # Always all LEDs
After: All endpoints support LED targeting
# Enhanced: All endpoints support LED parameter
curl "http://localhost:8000/lights/on?color=red&led=1"
curl "http://localhost:8000/lights/blink?color=blue&led=2"
# Effects now support LED targeting
curl "http://localhost:8000/lights/rainbow?led=1"
curl "http://localhost:8000/lights/pulse?color=red&led=2"
curl "http://localhost:8000/lights/fli?color_a=red&color_b=blue&led=1"
5. Custom Effect Development
Before: Effects couldn't handle LED targeting
class MyEffect(BaseEffect):
async def execute(self, light: "Light", interval: float | None = None) -> None:
# Old: No LED parameter support
for color in self.colors:
light.on(color) # Always all LEDs
await asyncio.sleep(interval or self.default_interval)
light.off() # Always all LEDs
After: Effects automatically support LED targeting
class MyEffect(BaseEffect):
async def execute(self, light: "Light", interval: float | None = None, led: int = 0) -> None:
# New: LED parameter automatically supported
for color in self.colors:
light.on(color, led=led) # Respects LED targeting
await asyncio.sleep(interval or self.default_interval)
light.off(led=led) # Cleans up specific LED
Breaking Changes
None! The migration is fully backward compatible.
What Doesn't Change
- Existing Python code continues to work unchanged
- Existing CLI commands work exactly the same
- Existing Web API calls work exactly the same
- Default behavior (all LEDs) remains the same
What's Enhanced
- All effects can now target specific LEDs via
led
parameter - More consistent API across all interfaces
- Better multi-LED device support
Implementation Details
Effect System Changes
Unified Architecture: The new system eliminates the special-case LED blink implementation:
# Before: Two different code paths
if led == 0:
effect = Effects.for_name("blink")(color, count=count)
return self.apply_effect(effect, interval=speed_obj.duty_cycle)
else:
return self._apply_led_blink(color, count, speed_obj.duty_cycle, led)
# After: Single unified path
effect = Effects.for_name("blink")(color, count=count)
return self.apply_effect(effect, interval=speed_obj.duty_cycle, led=led)
BaseEffect Enhancement: The execute method now includes LED parameter:
async def execute(self, light: "Light", interval: float | None = None, led: int = 0) -> None:
# LED parameter is now part of the core effect interface
for color in color_iterator:
light.on(color, led=led) # LED targeting built-in
await asyncio.sleep(sleep_interval)
API Signature Changes
All apply_effect methods now accept LED parameter:
# Controller
def apply_effect(self, effect, duration=None, interval=None, led=0) -> LightSelection
# BusyLightAPI
async def apply_effect(self, effect, light_id=None, led=0) -> None
Migration Checklist
For Users
- [ ] No action required - existing code continues to work
- [ ] Optional: Add
--led
parameter to CLI commands for multi-LED targeting - [ ] Optional: Add
led
parameter to Web API calls for LED-specific effects
For Developers
- [ ] No action required - existing Python code continues to work
- [ ] Recommended: Update custom effects to accept
led
parameter in execute method - [ ] Optional: Enhance applications to support multi-LED targeting
For Custom Effect Authors
- [ ] Update execute signature to include
led: int = 0
parameter - [ ] Pass LED parameter to
light.on()
andlight.off()
calls - [ ] Test with multi-LED devices if available
Examples
Simple Migration Example
Before:
# Your existing code
with LightController() as controller:
controller.all().turn_on("red")
controller.all().apply_effect(Spectrum())
After (unchanged - still works):
# Same code works exactly the same
with LightController() as controller:
controller.all().turn_on("red")
controller.all().apply_effect(Spectrum())
Enhanced (new capability):
# New LED targeting capabilities
with LightController() as controller:
controller.all().turn_on("red", led=1) # Top LED red
controller.all().apply_effect(Spectrum(), led=2) # Bottom LED rainbow
Custom Effect Migration
Before:
class BlinkingGradient(BaseEffect):
def __init__(self, color):
self.color = color
self.priority = TaskPriority.NORMAL
@property
def colors(self):
return [self.color, (0, 0, 0)] # Color + black
@property
def default_interval(self):
return 0.5
After (LED-aware):
class BlinkingGradient(BaseEffect):
def __init__(self, color):
self.color = color
self.priority = TaskPriority.NORMAL
@property
def colors(self):
return [self.color, (0, 0, 0)] # Color + black
@property
def default_interval(self):
return 0.5
# Optional: Custom execute method with LED support
async def execute(self, light: "Light", interval: float | None = None, led: int = 0) -> None:
"""Custom execution with LED awareness."""
try:
for color in self.colors:
light.on(color, led=led) # Pass LED parameter
await asyncio.sleep(interval or self.default_interval)
finally:
light.off(led=led) # Clean up specific LED
Testing
Backward Compatibility Tests
All existing functionality is tested to ensure no regressions:
# These tests pass with both old and new systems
def test_backward_compatibility():
effect = Steady((255, 0, 0))
mock_light = Mock()
# Old usage still works
asyncio.run(effect.execute(mock_light))
mock_light.on.assert_called_with((255, 0, 0), led=0)
New LED Functionality Tests
def test_led_targeting():
effect = Steady((255, 0, 0))
mock_light = Mock()
# New LED targeting works
asyncio.run(effect.execute(mock_light, led=1))
mock_light.on.assert_called_with((255, 0, 0), led=1)
Support
If you encounter any issues during migration:
- Backward Compatibility: All existing code should work unchanged
- New Features: LED targeting is additive - use
led
parameter when needed - Documentation: Updated examples show both old and new usage patterns
- Testing: Comprehensive test suite ensures reliability
The LED-aware effects system enhances BusyLight's capabilities while maintaining full compatibility with existing code and usage patterns.