#!/usr/bin/env python3 """Marcus animated face on the Shining LED mask. Connects to the mask, uploads a face frame-set once (DIY images, persisted on the mask's flash), then runs a live animated face: idle blinking + glancing, and a moving mouth while "speaking" -- all via fast PLAY commands (no upload logo). Usage: python main.py # demo: idle, then a simulated talking burst python main.py --reload # force re-uploading the frame set python main.py --address AA:BB:CC:DD:EE:FF python main.py --talk # start speaking immediately and stay talking Marcus / Sanad integration: face = FaceAnimator(mask); await face.start() face.set_speaking(True) # call when TTS playback starts face.set_speaking(False) # call when it ends # ...or face.set_mouth(0..3) from live audio amplitude for rough lip-sync. """ import argparse import asyncio from faceanim import FaceAnimator from mask import ShiningMask from exceptions import MaskNotFound async def run(args): mask = ShiningMask(address=args.address, name_prefix=args.name_prefix) print("connecting to mask ...") try: await mask.connect(timeout=20.0, attempts=8) except MaskNotFound as exc: print(f"could not find the mask: {exc}") return print("connected.") face = FaceAnimator(mask, fps=args.fps, brightness=args.brightness) print("loading face frames (one-time ~25s if not already on the mask) ...") await face.start(reload=args.reload) print("face is live. Ctrl+C to stop.") try: if args.talk: face.set_speaking(True) while True: await asyncio.sleep(1.0) else: # demo: alternate idle and 'speaking' so you can see both modes while True: print(" [idle] blinking / glancing ~6s") await asyncio.sleep(6) print(" [speaking] mouth animating ~6s") face.set_speaking(True) await asyncio.sleep(6) face.set_speaking(False) except KeyboardInterrupt: print("\nstopping ...") finally: await face.stop() await mask.disconnect() def main(): ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("--address", help="connect to a specific BLE MAC instead of scanning") ap.add_argument("--name-prefix", default="MASK") ap.add_argument("--reload", action="store_true", help="force re-upload of the frame set") ap.add_argument("--talk", action="store_true", help="start in talking mode and stay there") ap.add_argument("--fps", type=float, default=8.0) ap.add_argument("--brightness", type=int, default=95) asyncio.run(run(ap.parse_args())) if __name__ == "__main__": main()