79 lines
2.8 KiB
Python
79 lines
2.8 KiB
Python
#!/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()
|