@@ -643,6 +643,212 @@ def defocus_handler(self, obj):
643643- ` mpos.clipboard ` : System clipboard access
644644- ` mpos.battery_voltage ` : Battery level reading (ESP32 only)
645645
646+ ## Audio System (AudioFlinger)
647+
648+ MicroPythonOS provides a centralized audio service called ** AudioFlinger** (Android-inspired) that manages audio playback across different hardware outputs.
649+
650+ ### Supported Audio Devices
651+
652+ - ** I2S** : Digital audio output for WAV file playback (Fri3d badge, Waveshare board)
653+ - ** Buzzer** : PWM-based tone/ringtone playback (Fri3d badge only)
654+ - ** Both** : Simultaneous I2S and buzzer support
655+ - ** Null** : No audio (desktop/Linux)
656+
657+ ### Basic Usage
658+
659+ ** Playing WAV files** :
660+ ``` python
661+ import mpos.audio.audioflinger as AudioFlinger
662+
663+ # Play music file
664+ success = AudioFlinger.play_wav(
665+ " M:/sdcard/music/song.wav" ,
666+ stream_type = AudioFlinger.STREAM_MUSIC ,
667+ volume = 80 ,
668+ on_complete = lambda msg : print (msg)
669+ )
670+
671+ if not success:
672+ print (" Audio playback rejected (higher priority stream active)" )
673+ ```
674+
675+ ** Playing RTTTL ringtones** :
676+ ``` python
677+ # Play notification sound via buzzer
678+ rtttl = " Nokia:d=4,o=5,b=225:8e6,8d6,8f#,8g#,8c#6,8b,d,8p,8b,8a,8c#,8e"
679+ AudioFlinger.play_rtttl(
680+ rtttl,
681+ stream_type = AudioFlinger.STREAM_NOTIFICATION
682+ )
683+ ```
684+
685+ ** Volume control** :
686+ ``` python
687+ AudioFlinger.set_volume(70 ) # 0-100
688+ volume = AudioFlinger.get_volume()
689+ ```
690+
691+ ** Stopping playback** :
692+ ``` python
693+ AudioFlinger.stop()
694+ ```
695+
696+ ### Audio Focus Priority
697+
698+ AudioFlinger implements priority-based audio focus (Android-inspired):
699+ - ** STREAM_ALARM** (priority 2): Highest priority
700+ - ** STREAM_NOTIFICATION** (priority 1): Medium priority
701+ - ** STREAM_MUSIC** (priority 0): Lowest priority
702+
703+ Higher priority streams automatically interrupt lower priority streams. Equal or lower priority streams are rejected while a stream is playing.
704+
705+ ### Hardware Support Matrix
706+
707+ | Board | I2S | Buzzer | LEDs |
708+ | -------| -----| --------| ------|
709+ | Fri3d 2024 Badge | ✓ (GPIO 2, 47, 16) | ✓ (GPIO 46) | ✓ (5 RGB, GPIO 12) |
710+ | Waveshare ESP32-S3 | ✓ (GPIO 2, 47, 16) | ✗ | ✗ |
711+ | Linux/macOS | ✗ | ✗ | ✗ |
712+
713+ ### Configuration
714+
715+ Audio device preference is configured in Settings app under "Advanced Settings":
716+ - ** Auto-detect** : Use available hardware (default)
717+ - ** I2S (Digital Audio)** : Digital audio only
718+ - ** Buzzer (PWM Tones)** : Tones/ringtones only
719+ - ** Both I2S and Buzzer** : Use both devices
720+ - ** Disabled** : No audio
721+
722+ ** Note** : Changing the audio device requires a restart to take effect.
723+
724+ ### Implementation Details
725+
726+ - ** Location** : ` lib/mpos/audio/audioflinger.py `
727+ - ** Pattern** : Module-level singleton (similar to ` battery_voltage.py ` )
728+ - ** Thread-safe** : Uses locks for concurrent access
729+ - ** Background playback** : Runs in separate thread
730+ - ** WAV support** : 8/16/24/32-bit PCM, mono/stereo, auto-upsampling to ≥22050 Hz
731+ - ** RTTTL parser** : Full Ring Tone Text Transfer Language support with exponential volume curve
732+
733+ ## LED Control (LightsManager)
734+
735+ MicroPythonOS provides a simple LED control service for NeoPixel RGB LEDs (Fri3d badge only).
736+
737+ ### Basic Usage
738+
739+ ** Check availability** :
740+ ``` python
741+ import mpos.lights as LightsManager
742+
743+ if LightsManager.is_available():
744+ print (f " LEDs available: { LightsManager.get_led_count()} " )
745+ ```
746+
747+ ** Control individual LEDs** :
748+ ``` python
749+ # Set LED 0 to red (buffered)
750+ LightsManager.set_led(0 , 255 , 0 , 0 )
751+
752+ # Set LED 1 to green
753+ LightsManager.set_led(1 , 0 , 255 , 0 )
754+
755+ # Update hardware
756+ LightsManager.write()
757+ ```
758+
759+ ** Control all LEDs** :
760+ ``` python
761+ # Set all LEDs to blue
762+ LightsManager.set_all(0 , 0 , 255 )
763+ LightsManager.write()
764+
765+ # Clear all LEDs (black)
766+ LightsManager.clear()
767+ LightsManager.write()
768+ ```
769+
770+ ** Notification colors** :
771+ ``` python
772+ # Convenience method for common colors
773+ LightsManager.set_notification_color(" red" )
774+ LightsManager.set_notification_color(" green" )
775+ # Available: red, green, blue, yellow, orange, purple, white
776+ ```
777+
778+ ### Custom Animations
779+
780+ LightsManager provides one-shot control only (no built-in animations). Apps implement custom animations using the ` update_frame() ` pattern:
781+
782+ ``` python
783+ import time
784+ import mpos.lights as LightsManager
785+
786+ def blink_pattern ():
787+ for _ in range (5 ):
788+ LightsManager.set_all(255 , 0 , 0 )
789+ LightsManager.write()
790+ time.sleep_ms(200 )
791+
792+ LightsManager.clear()
793+ LightsManager.write()
794+ time.sleep_ms(200 )
795+
796+ def rainbow_cycle ():
797+ colors = [
798+ (255 , 0 , 0 ), # Red
799+ (255 , 128 , 0 ), # Orange
800+ (255 , 255 , 0 ), # Yellow
801+ (0 , 255 , 0 ), # Green
802+ (0 , 0 , 255 ), # Blue
803+ ]
804+
805+ for i, color in enumerate (colors):
806+ LightsManager.set_led(i, * color)
807+
808+ LightsManager.write()
809+ ```
810+
811+ ** For frame-based LED animations** , use the TaskHandler event system:
812+
813+ ``` python
814+ import mpos.ui
815+ import time
816+
817+ class LEDAnimationActivity (Activity ):
818+ last_time = 0
819+ led_index = 0
820+
821+ def onResume (self , screen ):
822+ self .last_time = time.ticks_ms()
823+ mpos.ui.task_handler.add_event_cb(self .update_frame, 1 )
824+
825+ def onPause (self , screen ):
826+ mpos.ui.task_handler.remove_event_cb(self .update_frame)
827+ LightsManager.clear()
828+ LightsManager.write()
829+
830+ def update_frame (self , a , b ):
831+ current_time = time.ticks_ms()
832+ delta_time = time.ticks_diff(current_time, self .last_time) / 1000.0
833+ self .last_time = current_time
834+
835+ # Update animation every 0.5 seconds
836+ if delta_time > 0.5 :
837+ LightsManager.clear()
838+ LightsManager.set_led(self .led_index, 0 , 255 , 0 )
839+ LightsManager.write()
840+ self .led_index = (self .led_index + 1 ) % LightsManager.get_led_count()
841+ ```
842+
843+ ### Implementation Details
844+
845+ - ** Location** : ` lib/mpos/lights.py `
846+ - ** Pattern** : Module-level singleton (similar to ` battery_voltage.py ` )
847+ - ** Hardware** : 5 NeoPixel RGB LEDs on GPIO 12 (Fri3d badge)
848+ - ** Buffered** : LED colors are buffered until ` write() ` is called
849+ - ** Thread-safe** : No locking (single-threaded usage recommended)
850+ - ** Desktop** : Functions return ` False ` (no-op) on desktop builds
851+
646852## Animations and Game Loops
647853
648854MicroPythonOS supports frame-based animations and game loops using the TaskHandler event system. This pattern is used for games, particle effects, and smooth animations.
0 commit comments