#!/usr/bin/env python3 """ Simple Oscillator Sensor Demo This script demonstrates the oscillator sensor by: 1. Creating an oscillator sensor with various waveforms 2. Printing the waveform data in real-time Usage: uv run python scripts/demo_oscillator_simple.py --waveform sine --frequency 1.0 uv run python scripts/demo_oscillator_simple.py --waveform square --frequency 2.0 """ import argparse import math import time import sys from pathlib import Path # Add mainline to path sys.path.insert(0, str(Path(__file__).parent.parent)) from engine.sensors.oscillator import OscillatorSensor, register_oscillator_sensor def render_waveform(width: int, height: int, osc: OscillatorSensor, frame: int) -> str: """Render a waveform visualization.""" # Get current reading current_reading = osc.read() current_value = current_reading.value if current_reading else 0.0 # Generate waveform data - sample the waveform function directly # This shows what the waveform looks like, not the live reading samples = [] waveform_fn = osc.WAVEFORMS[osc._waveform] for i in range(width): # Sample across one complete cycle (0 to 1) phase = i / width value = waveform_fn(phase) samples.append(value) # Build visualization lines = [] # Header with sensor info header = ( f"Oscillator: {osc.name} | Waveform: {osc.waveform} | Freq: {osc.frequency}Hz" ) lines.append(header) lines.append("─" * width) # Waveform plot (scaled to fit height) num_rows = height - 3 # Header, separator, footer for row in range(num_rows): # Calculate the sample value that corresponds to this row # 0.0 is bottom, 1.0 is top row_value = 1.0 - (row / (num_rows - 1)) if num_rows > 1 else 0.5 line_chars = [] for x, sample in enumerate(samples): # Determine if this sample should be drawn in this row # Map sample (0.0-1.0) to row (0 to num_rows-1) # 0.0 -> row 0 (bottom), 1.0 -> row num_rows-1 (top) sample_row = int(sample * (num_rows - 1)) if sample_row == row: # Use different characters for waveform vs current position marker # Check if this is the current reading position if abs(x / width - (osc._phase % 1.0)) < 0.02: line_chars.append("◎") # Current position marker else: line_chars.append("█") else: line_chars.append(" ") lines.append("".join(line_chars)) # Footer with current value and phase info footer = f"Value: {current_value:.3f} | Frame: {frame} | Phase: {osc._phase:.2f}" lines.append(footer) return "\n".join(lines) def demo_oscillator(waveform: str = "sine", frequency: float = 1.0, frames: int = 0): """Run oscillator demo.""" print(f"Starting oscillator demo: {waveform} wave at {frequency}Hz") if frames > 0: print(f"Running for {frames} frames") else: print("Press Ctrl+C to stop") print() # Create oscillator sensor register_oscillator_sensor(name="demo_osc", waveform=waveform, frequency=frequency) osc = OscillatorSensor(name="demo_osc", waveform=waveform, frequency=frequency) osc.start() # Run demo loop try: frame = 0 while frames == 0 or frame < frames: # Render waveform visualization = render_waveform(80, 20, osc, frame) # Print with ANSI escape codes to clear screen and move cursor print("\033[H\033[J" + visualization) time.sleep(0.05) # 20 FPS frame += 1 except KeyboardInterrupt: print("\n\nDemo stopped by user") finally: osc.stop() if __name__ == "__main__": parser = argparse.ArgumentParser(description="Oscillator sensor demo") parser.add_argument( "--frames", type=int, default=0, help="Number of frames to render (0 = infinite until Ctrl+C)", ) parser.add_argument( "--frequency", type=float, default=1.0, help="Oscillator frequency in Hz" ) parser.add_argument( "--frames", type=int, default=100, help="Number of frames to render" ) args = parser.parse_args() demo_oscillator(args.waveform, args.frequency, args.frames)