#!/usr/bin/env python3 """Simulated ESP32 sensor rig: a virtual hand gliding over a VL53L5CX ToF matrix plus a BH1750 ambient-light cell. It is musical because the smooth Lissajous motion gives the visualizer a living, breathing performer to react to before any real hardware exists — hand position and light become continuous playable knobs. """ import argparse import math import random import time from pythonosc.udp_client import SimpleUDPClient NAME = "sim_esp32" RATE_HZ = 60.0 DT = 1.0 / RATE_HZ def clamp(v, lo, hi): return lo if v < lo else hi if v > hi else v def main(): ap = argparse.ArgumentParser(description="Simulate the future ESP32 ToF + light sensor rig.") ap.add_argument("--host", default="127.0.0.1") ap.add_argument("--port", type=int, default=9000) args = ap.parse_args() client = SimpleUDPClient(args.host, args.port) # A fixed phase offset so multiple instances don't march in lockstep. seed = random.random() * 1000.0 t0 = time.time() # Schedule the first simulated flash 8-20s out. next_flash = t0 + random.uniform(8.0, 20.0) print(f"[{NAME}] emitting /gs/tof/*, /gs/light/* every {DT:.4f}s") while True: try: now = time.time() t = (now - t0) + seed # --- Hand X/Y: sums of a few sines at irrational-ish rates + slow drift. # Lissajous-feel motion, never white noise. Kept smoothly inside 0..1. cx = ( 0.50 + 0.30 * math.sin(t * 0.31700) + 0.12 * math.sin(t * 0.71300 + 1.3) + 0.06 * math.sin(t * 1.61803 + 2.1) # golden-ratio wobble ) cy = ( 0.50 + 0.28 * math.sin(t * 0.27100 + 0.7) + 0.13 * math.sin(t * 0.61800 + 2.4) # 1/phi + 0.05 * math.sin(t * 1.41421 + 0.2) # sqrt(2) ) # --- Proximity: slow breathing in/out, hand drifting toward/away. near = ( 0.50 + 0.35 * math.sin(t * 0.19000) + 0.10 * math.sin(t * 0.53000 + 1.1) ) # --- Ambient light: drifts in the hundreds of lux, with slow occasional dips. base = 500.0 + 250.0 * math.sin(t * 0.041 + 0.5) + 90.0 * math.sin(t * 0.113 + 2.0) # A slow, mostly-shallow dip that deepens rarely (raising to a power keeps it near 1 # most of the time and only occasionally sinks toward a shadow). dip = 0.55 + 0.45 * math.sin(t * 0.023 + 1.7) dip = clamp(dip, 0.0, 1.0) ** 3 # mostly bright, sometimes dark lux = clamp(base * (0.35 + 0.65 * dip), 0.0, 1000.0) client.send_message("/gs/tof/cx", clamp(cx, 0.0, 1.0)) client.send_message("/gs/tof/cy", clamp(cy, 0.0, 1.0)) client.send_message("/gs/tof/near", clamp(near, 0.0, 1.0)) client.send_message("/gs/light/lux", lux) # --- Occasional strobe/camera flash event. if now >= next_flash: client.send_message("/gs/light/flash/event", 1.0) next_flash = now + random.uniform(8.0, 20.0) except OSError as e: # Transient network/socket hiccup: warn, back off briefly, keep going forever. print(f"[{NAME}] send failed ({e}); retrying") time.sleep(0.5) continue time.sleep(DT) if __name__ == "__main__": main()