@@ -168,6 +168,231 @@ busylight/
168
168
- ** Async/Await** : Non-blocking effects and concurrent operations
169
169
- ** Dependency Injection** : Testable components with clear interfaces
170
170
171
+ ## Effects Development
172
+
173
+ The Effects system is the core of BusyLight's dynamic lighting capabilities.
174
+ Understanding how to create and modify effects is essential for contributors.
175
+
176
+ ### Effects Architecture
177
+
178
+ All effects inherit from ` BaseEffect ` in ` src/busylight/effects/effect.py ` :
179
+
180
+ ``` python
181
+ class BaseEffect (abc .ABC ):
182
+ @ property
183
+ @abc.abstractmethod
184
+ def colors (self ) -> list[tuple[int , int , int ]]:
185
+ """ List of RGB color tuples defining the effect sequence."""
186
+
187
+ @ property
188
+ @abc.abstractmethod
189
+ def default_interval (self ) -> float :
190
+ """ Default interval between color changes in seconds."""
191
+ ```
192
+
193
+ ### Built-in Effects
194
+
195
+ - ** ` Steady ` ** : Static color display
196
+ - ** ` Blink ` ** : Alternates between two colors
197
+ - ** ` Spectrum ` ** : Rainbow color cycling with sine wave generation
198
+ - ** ` Gradient ` ** : Smooth fade from black to color and back
199
+
200
+ ### Creating New Effects
201
+
202
+ #### 1. Basic Effect Structure
203
+
204
+ ``` python
205
+ from typing import TYPE_CHECKING
206
+ from busylight_core.mixins.taskable import TaskPriority
207
+ from busylight.effects.effect import BaseEffect
208
+
209
+ if TYPE_CHECKING :
210
+ from busylight_core import Light
211
+
212
+ class MyEffect (BaseEffect ):
213
+ def __init__ (self , color : tuple[int , int , int ]) -> None :
214
+ self .color = color
215
+ self .priority = TaskPriority.NORMAL
216
+
217
+ @ property
218
+ def colors (self ) -> list[tuple[int , int , int ]]:
219
+ # Return your color sequence
220
+ return [self .color, (0 , 0 , 0 )] # Color + black
221
+
222
+ @ property
223
+ def default_interval (self ) -> float :
224
+ return 0.5 # 500ms between changes
225
+ ```
226
+
227
+ #### 2. Color Generation Patterns
228
+
229
+ ** Static Sequences:**
230
+ ``` python
231
+ @ property
232
+ def colors (self ) -> list[tuple[int , int , int ]]:
233
+ return [(255 , 0 , 0 ), (0 , 255 , 0 ), (0 , 0 , 255 )]
234
+ ```
235
+
236
+ ** Computed Sequences:**
237
+ ``` python
238
+ @ property
239
+ def colors (self ) -> list[tuple[int , int , int ]]:
240
+ if hasattr (self , " _colors" ):
241
+ return self ._colors
242
+
243
+ # Generate color sequence
244
+ colors = []
245
+ for i in range (10 ):
246
+ intensity = int (255 * (i / 10 ))
247
+ colors.append((intensity, 0 , 0 ))
248
+
249
+ self ._colors = colors # Cache result
250
+ return self ._colors
251
+ ```
252
+
253
+ ** Mathematical Functions:**
254
+ ``` python
255
+ import math
256
+
257
+ @ property
258
+ def colors (self ) -> list[tuple[int , int , int ]]:
259
+ colors = []
260
+ for i in range (50 ):
261
+ # Sine wave for smooth transitions
262
+ r = int (127 * math.sin(i * 0.1 ) + 128 )
263
+ g = int (127 * math.cos(i * 0.1 ) + 128 )
264
+ colors.append((r, g, 0 ))
265
+ return colors
266
+ ```
267
+
268
+ #### 3. Advanced Execution Control
269
+
270
+ For complex timing or custom logic, override ` execute() ` :
271
+
272
+ ``` python
273
+ async def execute (self , light : " Light" , interval : float | None = None ) -> None :
274
+ """ Custom execution with variable timing."""
275
+ try :
276
+ for i, color in enumerate (self .colors):
277
+ light.on(color)
278
+
279
+ # Variable timing based on position
280
+ if i % 2 == 0 :
281
+ await asyncio.sleep(0.1 ) # Fast
282
+ else :
283
+ await asyncio.sleep(0.5 ) # Slow
284
+
285
+ finally :
286
+ light.off() # Always cleanup
287
+ ```
288
+
289
+ ### Effect Development Guidelines
290
+
291
+ #### Performance Best Practices
292
+
293
+ 1 . ** Cache Color Calculations** : Use ` self._colors ` to avoid recomputation
294
+ 2 . ** Reasonable Sequence Length** : Keep under 1000 colors for performance
295
+ 3 . ** Appropriate Timing** : Balance smoothness with device capabilities
296
+ 4 . ** Memory Efficiency** : Generate colors lazily when possible
297
+
298
+ #### Testing Effects
299
+
300
+ ** Unit Tests:**
301
+ ``` python
302
+ def test_effect_colors ():
303
+ effect = MyEffect(color = (255 , 0 , 0 ))
304
+ colors = effect.colors
305
+
306
+ assert len (colors) > 0
307
+ assert all (len (color) == 3 for color in colors)
308
+ assert all (0 <= c <= 255 for color in colors for c in color)
309
+
310
+ async def test_effect_execution ():
311
+ effect = MyEffect(color = (255 , 0 , 0 ))
312
+ mock_light = Mock()
313
+
314
+ await effect.execute(mock_light)
315
+ assert mock_light.on.called
316
+ assert mock_light.off.called
317
+ ```
318
+
319
+ ** Integration Testing:**
320
+ ``` bash
321
+ # Test with actual hardware
322
+ busylight --debug on red # Test steady effect
323
+ busylight --debug blink blue --count 3 # Test blink effect
324
+ ```
325
+
326
+ #### Effect Registration
327
+
328
+ Add new effects to ` src/busylight/effects/__init__.py ` :
329
+
330
+ ``` python
331
+ from .my_effect import MyEffect
332
+
333
+ __all__ = [
334
+ " Blink" ,
335
+ " Effects" ,
336
+ " Gradient" ,
337
+ " MyEffect" , # Add here
338
+ " Spectrum" ,
339
+ " Steady" ,
340
+ ]
341
+ ```
342
+
343
+ #### CLI Integration
344
+
345
+ Effects are automatically available via the discovery system:
346
+
347
+ ``` python
348
+ # In CLI code
349
+ effect_class = BaseEffect.for_name(" myeffect" )
350
+ effect = effect_class(color = (255 , 0 , 0 ))
351
+ ```
352
+
353
+ ### Effect Debugging
354
+
355
+ #### Common Issues
356
+
357
+ ** Colors Not Displaying:**
358
+ - Verify RGB values are 0-255
359
+ - Check that ` colors ` property returns non-empty list
360
+ - Ensure ` default_interval ` > 0
361
+
362
+ ** Performance Problems:**
363
+ - Profile color generation with large sequences
364
+ - Cache expensive computations in ` _colors `
365
+ - Use appropriate sleep intervals
366
+
367
+ ** Memory Leaks:**
368
+ - Clear cached data in ` reset() ` method if needed
369
+ - Avoid circular references in effect objects
370
+
371
+ #### Debug Utilities
372
+
373
+ ``` python
374
+ # Add to effect class for debugging
375
+ def debug_info (self ) -> dict :
376
+ return {
377
+ " name" : self .name,
378
+ " color_count" : len (self .colors),
379
+ " interval" : self .default_interval,
380
+ " priority" : self .priority.name,
381
+ }
382
+ ```
383
+
384
+ ### Effect Examples
385
+
386
+ See ** [ complete effects documentation] [ docs-effects ] ** for:
387
+ - Detailed API reference
388
+ - Advanced color generation techniques
389
+ - Mathematical effect patterns
390
+ - Performance optimization strategies
391
+ - Integration examples
392
+
393
+ Contributing new effects expands BusyLight's creative possibilities and
394
+ benefits the entire community!
395
+
171
396
## Development Workflow
172
397
173
398
### 1. Issue First
@@ -489,6 +714,7 @@ control accessible to developers worldwide.
489
714
[ discussions ] : https://github.com/JnyJny/busylight/discussions
490
715
[ device-request ] : https://github.com/JnyJny/busylight-core/issues/new?template=4_new_device_request.yaml
491
716
[ docs ] : https://jnyjny.github.io/busylight/
717
+ [ docs-effects ] : https://jnyjny.github.io/busylight/effects/
492
718
[ semver ] : https://semver.org/
493
719
[ sphinx-docstrings ] : https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html
494
720
[ coc ] : https://www.contributor-covenant.org/version/2/1/code_of_conduct/
0 commit comments