Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,10 @@ busylight blink blue # Blinking blue
busylight off # Turn off

# Multi-LED devices (Blink1 mk2, BlinkStick, etc.)
busylight on red --led 1 # First LED only
busylight on blue --led 2 # Second LED only
busylight on red --led 1 # First LED only
busylight on blue --led 2 # Second LED only
busylight rainbow --led 1 # Rainbow on first LED
busylight pulse green --led 2 # Pulse on second LED
```

### Web API
Expand All @@ -104,6 +106,10 @@ busyserve
curl "http://localhost:8000/light/0/on?color=red"
curl "http://localhost:8000/light/0/blink?color=blue&count=5"
curl "http://localhost:8000/lights/off"

# Multi-LED targeting
curl "http://localhost:8000/lights/on?color=red&led=1"
curl "http://localhost:8000/lights/rainbow?led=2"
```

## Documentation
Expand Down
47 changes: 47 additions & 0 deletions docs/cli/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,53 @@ busylight --dim 0.5 on red # 50% brightness
busylight --dim 0.1 on blue # 10% brightness
```

## Multi-LED Device Control

For devices with multiple LEDs (like Blink1 mk2), you can target specific LEDs:

### LED Targeting Basics

```bash
# Control all LEDs (default)
busylight on red

# Target specific LEDs
busylight on red --led 1 # First/top LED only
busylight on blue --led 2 # Second/bottom LED only

# Different colors on different LEDs
busylight on red --led 1 # Top LED red
busylight on blue --led 2 # Bottom LED blue
```

### Multi-LED Effects

```bash
# Different effects on different LEDs
busylight blink green --led 1 --count 3 # Top LED blinks
busylight pulse orange --led 2 # Bottom LED pulses

# Rainbow on specific LED
busylight rainbow --led 1 --speed fast # Top LED rainbow

# LED-specific patterns
busylight fli red blue --led 1 --count 5 # Flash top LED
busylight pulse red --led 2 --speed slow # Pulse bottom LED
```

### Sequential LED Programming

```bash
# Turn on LEDs one by one
busylight on red --led 1
sleep 1
busylight on blue --led 2

# Background effects
busylight rainbow --led 1 & # Top LED rainbow in background
busylight pulse red --led 2 # Bottom LED pulse in foreground
```

## Effects and Animation

### Blinking Patterns
Expand Down
315 changes: 315 additions & 0 deletions docs/contributing/led-aware-effects-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
# 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

1. **LED Targeting**: All effects can now target specific LEDs using the `led` parameter
2. **Unified System**: Removed special-case LED blink implementation - all effects use the same system
3. **API Consistency**: LED parameter is now available across CLI, Web API, and Python API
4. **Backward Compatible**: Existing code continues to work without changes

## Migration Scenarios

### 1. Basic Effect Usage (No Changes Required)

**Before and After - Identical:**
```python
# 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
```python
# 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
```python
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```python
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
```python
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:

```python
# 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:

```python
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:

```python
# 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()` and `light.off()` calls
- [ ] **Test with multi-LED devices** if available

## Examples

### Simple Migration Example

**Before:**
```python
# Your existing code
with LightController() as controller:
controller.all().turn_on("red")
controller.all().apply_effect(Spectrum())
```

**After (unchanged - still works):**
```python
# Same code works exactly the same
with LightController() as controller:
controller.all().turn_on("red")
controller.all().apply_effect(Spectrum())
```

**Enhanced (new capability):**
```python
# 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:**
```python
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):**
```python
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:

```python
# 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

```python
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:

1. **Backward Compatibility**: All existing code should work unchanged
2. **New Features**: LED targeting is additive - use `led` parameter when needed
3. **Documentation**: Updated examples show both old and new usage patterns
4. **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.
Loading
Loading