# Proximity Sensor

The Beyond uses an **AMS TMD2635** proximity sensor to detect when a user is wearing the headset. This drives automatic display on/off behavior.

## TMD2635 Proximity Sensor Driver

**I2C-based** ambient light and proximity sensor with IR VCSEL (Vertical-Cavity Surface-Emitting Laser) emitter.

### Configurable Parameters

All parameters settable via HID command `M` (0x4D):

| Parameter | Byte | Range | Description |
|-----------|------|-------|-------------|
| PWEN | 1 | 0–1 | Wait time enable |
| PRATE | 2 | 0–255 | Wait time between pulses. Time = (PRATE + 1) * 88 us |
| PWLONG | 3 | 0–1 | Long wait multiplier (12x when enabled) |
| PGAIN | 4 | 0–3 | IR sensor gain: 1x, 2x, 4x, 8x |
| PPULSE | 5 | 0–63 | Max VCSEL pulses per cycle (actual = PPULSE + 1) |
| PPULSE_LEN | 6 | 0–7 | Pulse length: 1, 2, 4, 8, 12, 16, 24, 32 us |
| PLDRIVE | 7 | 0–8 | VCSEL drive strength: 2–10 mA in 1 mA steps |
| PWTIME | 8 | 0–255 | Wait time between samples in 2.78 ms increments |
| PDSELECT | 9 | 1–3 | Photodiode: 1=Far, 2=Near, 3=Both |
| PMAVG | 10 | 0–3 | Firmware moving average: off, 2, 4, 8 values |
| PROX_AVG | 11 | 0–7 | Hardware averaging: off, 2, 4, 8, 16, 32, 64, 128 samples |

**Default photodiode:** Far (changed from Near in v0.2.3 for better sensitivity)

**Source files:** `src/Devices/prox_tmd2635.c/h`

## Proximity State Machine

The proximity control module implements a state machine with hysteresis:

- **Display ON** when: proximity value >= (threshold + hysteresis)
- **Display OFF** when: proximity value <= (threshold - hysteresis)
- Both threshold and hysteresis are configurable via config tags `0x0B` and `0x0C`

### Two-Stage Dimming (v0.2.7+)

Instead of immediately turning off displays (which causes a temporary blue color shift when they come back on):

1. **Immediate:** Dims to minimum possible brightness
2. **After 5 minutes:** Fully powers off displays

This significantly reduces how often users see the blue color shift.

### Sanity Checking (v0.3.7C+)

Incoming proximity sensor samples are validated:
- Values that are too low or too high are rejected
- Invalid samples do not update the proximity on/off state
- Prevents spurious display toggling from sensor glitches

### Disconnected Sensor Handling (v0.3.7+)

When the sensor is disconnected, the driver returns the **last valid reading** instead of zero. This prevents the displays from turning off due to a hardware fault.

## Calibration

- **Null offset:** Stored as config tag `0x06` (16-bit unsigned, LSB first)
- Represents the raw proximity value when nothing is present
- Subtracted from all readings as a baseline offset
- Calibrated using `scripts/prox_calibration.py`

## Enable / Disable

| Action | HID Command | Persistence |
|--------|-------------|-------------|
| Disable (session) | `p` (0x70) | Until next power cycle |
| Enable (session) | `[` (0x5B) | Re-enables after disable |
| Disable (permanent) | Config tag `0x04` set to non-zero | Survives power cycles |

When disabled, displays are forced on regardless of sensor readings.

## User Trim (v0.3.15)

A signed 16-bit proximity threshold adjustment can be applied via HID command and stored persistently:

- **HID command:** Sends a signed int16 (LSB first, bytes 1–2) that adjusts the proximity on/off threshold
- **Config tag:** `0x0E` (2 bytes, signed int16) — persists across power cycles
- Allows per-unit fine-tuning of proximity sensitivity without changing the base threshold or hysteresis
- Accessible via `scripts/prox_config.py` and `scripts/config_editor_imgui.py`

## Proximity Data in Telemetry

The periodic HID report (bytes 4–5) includes the raw proximity distance as a 16-bit unsigned integer (big-endian). The display status bitfield also reports:
- Bit 1: Proximity sensor detecting user
- Bit 2: 5-minute dim timer has expired

## Bug Fix History

| Version | Fix |
|---------|-----|
| v0.2.3 | Switched to FAR photodiode, added config memory calibration, averaging |
| v0.2.7 | Two-stage dimming (dim first, then off after 5 min) |
| v0.2.12 | Added session disable command |
| v0.2.15 | Fixed stack overflow in FreeRTOS timer task at 5-minute timer expiry |
| v0.3.4 | Proximity timer bugfix |
| v0.3.7 | Return last value when disconnected |
| v0.3.7C | Sanity checks on incoming samples |
| v0.3.7D–E | I2C scheduling fixes (interrupt-driven + semaphores) |

**Source files:** `src/Devices/prox_control.c/h`, `src/Devices/prox_tmd2635.c/h`  
**Python tools:** `scripts/prox_calibration.py`, `scripts/prox_config.py`, `scripts/prox_monitor.py`
