# MH4 Library - Complete Documentation

## 🚀 Quick Start

```javascript
import { MH4, Events } from 'https://developer.mariusheier.com/modules/core/mh4-hid-streamlined.js';

const mh4 = new MH4();

// CRITICAL: Controller must be in setup mode (100-300Hz)
// Hold PS button while connecting USB cable!

// Connect to controller
const connected = await mh4.connect();
if (!connected) {
    console.error('Failed to connect - check if controller is in setup mode');
    return;
}

// Listen for data updates (non-blocking)
mh4.addEventListener(Events.DATA_UPDATE, (e) => {
    const { type, data } = e.detail;
    console.log(`${type} updated:`, data);
});

// Get cached sensor data anytime (non-blocking)
const sensors = mh4.getSensorData('all');
```

## 📦 Module Overview

| Module | Purpose | CDN URL |
|--------|---------|---------|
| **mh4-hid-streamlined.js** | Core HID driver | `https://developer.mariusheier.com/modules/core/mh4-hid-streamlined.js` |
| **lut.js** | Stick calibration & transformation | `https://developer.mariusheier.com/modules/core/lut.js` |
| **data-store.js** | Browser storage wrapper | `https://developer.mariusheier.com/modules/core/data-store.js` |
| **mh4-led.js** | LED visualization control | `https://developer.mariusheier.com/modules/ui/mh4-led.js` |

## 🎮 HID Driver (`mh4-hid-streamlined.js`)

### Core Concepts

- **Non-blocking**: All sensor reads are cached, never blocks UI
- **Event-driven**: Subscribe to changes rather than polling
- **Setup Mode Required**: Controller MUST be in 100-300Hz mode

### Connection

```javascript
import { MH4, Events } from 'https://developer.mariusheier.com/modules/core/mh4-hid-streamlined.js';

const mh4 = new MH4();

// Check WebHID support
if (!MH4.isSupported()) {
    alert('WebHID not supported in this browser');
    return;
}

// Listen for connection events BEFORE connecting
mh4.addEventListener(Events.CONNECTED, (e) => {
    console.log('Connected!', e.detail);
    // e.detail contains: deviceName, firmwareVersion, pollRate, uid
});

mh4.addEventListener(Events.SETUP_MODE_REQUIRED, (e) => {
    alert(`Controller is at ${e.detail.pollRate}Hz. Hold PS button while connecting USB!`);
});

mh4.addEventListener(Events.ERROR, (e) => {
    console.error('Error:', e.detail.message);
});

// Connect (will prompt for device selection)
const success = await mh4.connect();
```

### Reading Sensors (Cached/Non-blocking)

```javascript
// Get all sensors at once
const all = mh4.getSensorData('all');
// Returns: {
//   leftStick: { x: 0-16383, y: 0-16383, timestamp },
//   rightStick: { x: 0-16383, y: 0-16383, timestamp },
//   triggers: { l2: 0-4095, r2: 0-4095, timestamp },
//   buttons: { extra1-6: boolean, timestamp }
// }

// Get specific sensors
const left = mh4.getSensorData('left-stick');
const right = mh4.getSensorData('right-stick');
const triggers = mh4.getSensorData('triggers');
const buttons = mh4.getSensorData('buttons');
```

### Event-Driven Updates

```javascript
// Listen for ANY sensor change
mh4.addEventListener(Events.DATA_UPDATE, (e) => {
    const { type, data, all } = e.detail;
    
    switch(type) {
        case 'left-stick':
            console.log('Left stick:', data.x, data.y);
            break;
        case 'right-stick':
            console.log('Right stick:', data.x, data.y);
            break;
        case 'triggers':
            console.log('Triggers:', data.l2, data.r2);
            break;
        case 'buttons':
            console.log('Buttons:', data);
            break;
    }
});
```

### Device Management

```javascript
// Get device info
const info = mh4.deviceInfo;
// Returns: { name, firmwareVersion, uid, pollRate }

// Check connection
const isConnected = mh4.connected;

// Disconnect
await mh4.disconnect();

// Reboot to normal mode (exits setup mode)
await mh4.reboot();
```

## 🎯 LUT Calibration (`lut.js`)

### Two Modes of Operation

1. **Learning Mode** (default) - Builds calibration as you use it
2. **Calibrated Mode** - Initialize with known values

### Learning Mode (Auto-Calibration)

```javascript
import { LUT } from 'https://developer.mariusheier.com/modules/core/lut.js';

const leftLUT = new LUT('left');
const rightLUT = new LUT('right');

// First value becomes center point
leftLUT.addValue(8192, 8192);

// Add more values as you move the stick
mh4.addEventListener(Events.DATA_UPDATE, (e) => {
    if (e.detail.type === 'left-stick') {
        const { x, y } = e.detail.data;
        leftLUT.addValue(x, y);
        
        // Get transformed values (-1 to +1 range)
        const transformed = leftLUT.getTransformed(x, y);
        console.log('Normalized:', transformed.x, transformed.y);
    }
});
```

### Calibrated Mode (Direct Initialization)

```javascript
const lut = new LUT('left');

// Initialize with known calibration
lut.initializeWithCalibration({
    center: { x: 8192, y: 8192 },
    xRange: { min: 0, max: 16383 },
    yRange: { min: 0, max: 16383 },
    invertX: true,
    invertY: false
});

// Now immediately ready to transform values
const transformed = lut.getTransformed(rawX, rawY);
```

### Response Curves

```javascript
// Built-in curves
lut.setActiveLUT('linear');     // No modification
lut.setActiveLUT('smooth');      // Exponential smoothing
lut.setActiveLUT('deadzone');    // 10% center deadzone
lut.setActiveLUT('precision');   // S-curve for fine control

// Check available curves
console.log(lut.availableLUTs); // ['linear', 'smooth', 'deadzone', 'precision']
```

### Dual Stick Support

```javascript
import { DualStickLUT } from 'https://developer.mariusheier.com/modules/core/lut.js';

const dualLUT = new DualStickLUT();

// Initialize both sticks at once
dualLUT.initializeWithCalibration({
    left: {
        center: { x: 8192, y: 8192 },
        xRange: { min: 0, max: 16383 },
        yRange: { min: 0, max: 16383 },
        invertX: true,
        invertY: false
    },
    right: {
        center: { x: 8192, y: 8192 },
        xRange: { min: 0, max: 16383 },
        yRange: { min: 0, max: 16383 },
        invertX: false,
        invertY: false
    }
});

// Transform values
const leftTransformed = dualLUT.getTransformed(x, y, 'left');
const rightTransformed = dualLUT.getTransformed(x, y, 'right');
```

## 💾 Data Storage (`data-store.js`)

### Basic Usage

```javascript
import { DataStore } from 'https://developer.mariusheier.com/modules/core/data-store.js';

const store = new DataStore('myapp');

// Save/load data (automatically JSON serialized)
store.set('calibration', { center: { x: 8192, y: 8192 } });
const calibration = store.get('calibration', null);

// Check existence
if (store.exists('calibration')) {
    // Load saved calibration
}

// Remove data
store.remove('calibration');

// Clear all app data
store.clear();
```

### Namespaced Storage

```javascript
// Sub-namespaces for organization
store.set('data', leftStickData, 'left-stick');
store.set('data', rightStickData, 'right-stick');

const leftData = store.get('data', null, 'left-stick');
```

## 💡 LED Control (`mh4-led.js`)

### Initialization

```javascript
import { MH4LED } from 'https://developer.mariusheier.com/modules/ui/mh4-led.js';

const led = new MH4LED();

// Initialize with container ID and options
led.init('led-container', {
    mode: 'breathing',        // 'off', 'static', 'breathing'
    intensity: 100,           // 0-100
    primaryColor: '#FF00FF',  // Hex color
    secondaryColor: '#00FFFF', // For breathing mode
    period: 3                 // Breathing period in seconds
});
```

### Control Methods

```javascript
// Change mode
led.setMode('static');        // 'off', 'static', 'breathing'

// Adjust intensity
led.setIntensity(75);         // 0-100

// Set colors
led.setColors('#FF0000', '#0000FF');  // primary, secondary

// Change breathing speed
led.setPeriod(2.5);           // seconds

// Get current state
const state = led.getState();
// Returns: { mode, intensity, primaryColor, secondaryColor, period }
```

### Event Handling

```javascript
// Listen for changes
led.on('mode-changed', (mode) => {
    console.log('Mode changed to:', mode);
});

led.on('intensity-changed', (intensity) => {
    console.log('Intensity:', intensity);
});

led.on('color-changed', ({ primary, secondary }) => {
    console.log('Colors:', primary, secondary);
});

led.on('state-changed', (state) => {
    console.log('Full state:', state);
});
```

### Cleanup

```javascript
// Properly destroy when done
led.destroy();
```

## ⚡ Complete Example

```javascript
import { MH4, Events } from 'https://developer.mariusheier.com/modules/core/mh4-hid-streamlined.js';
import { DualStickLUT } from 'https://developer.mariusheier.com/modules/core/lut.js';
import { DataStore } from 'https://developer.mariusheier.com/modules/core/data-store.js';
import { MH4LED } from 'https://developer.mariusheier.com/modules/ui/mh4-led.js';

// Initialize components
const mh4 = new MH4();
const lut = new DualStickLUT();
const store = new DataStore('mh4-app');
const led = new MH4LED();

// Setup LED visualization
led.init('led-container', {
    mode: 'breathing',
    intensity: 50,
    primaryColor: '#667eea'
});

// Load saved calibration if exists
const savedCalibration = store.get('calibration');
if (savedCalibration) {
    lut.initializeWithCalibration(savedCalibration);
    console.log('Loaded saved calibration');
}

// Connect to controller
async function connect() {
    // Listen for events
    mh4.addEventListener(Events.CONNECTED, (e) => {
        console.log('Connected:', e.detail.deviceName);
        led.setMode('static');
        led.setIntensity(100);
    });
    
    mh4.addEventListener(Events.SETUP_MODE_REQUIRED, (e) => {
        alert(`Controller at ${e.detail.pollRate}Hz - Hold PS button while connecting!`);
    });
    
    mh4.addEventListener(Events.DATA_UPDATE, (e) => {
        const { type, data } = e.detail;
        
        if (type === 'left-stick' || type === 'right-stick') {
            const stick = type.split('-')[0];
            
            // Add to LUT if in learning mode
            if (!savedCalibration) {
                lut[stick].addValue(data.x, data.y);
            }
            
            // Get transformed values
            const transformed = lut.getTransformed(data.x, data.y, stick);
            
            // Use transformed values
            console.log(`${stick}:`, transformed.x.toFixed(2), transformed.y.toFixed(2));
            
            // Update LED intensity based on stick movement
            const magnitude = Math.sqrt(transformed.x ** 2 + transformed.y ** 2);
            led.setIntensity(Math.min(100, magnitude * 100));
        }
        
        if (type === 'triggers') {
            // Map trigger values to LED colors
            const hue = (data.l2 / 4095) * 360;
            const color = `hsl(${hue}, 100%, 50%)`;
            led.setColors(color, color);
        }
    });
    
    // Attempt connection
    const success = await mh4.connect();
    if (!success) {
        led.setMode('off');
        console.error('Connection failed');
    }
}

// Save calibration before leaving
window.addEventListener('beforeunload', () => {
    if (lut.left.hasData || lut.right.hasData) {
        store.set('calibration', lut.serialize());
    }
});

// Start
connect();
```

## 🔧 Troubleshooting

### Controller Won't Connect
- **Hold PS button** while connecting USB cable to enter setup mode
- Controller must be at 100-300Hz (setup mode) to work
- Check browser supports WebHID (Chrome/Edge only)
- Must use HTTPS or localhost

### Stuttering/Performance Issues
- Use `getSensorData()` for cached reads (non-blocking)
- Avoid `readSensorDirect()` in loops
- Use event listeners instead of polling

### LED Not Working
- LED module creates its own UI - needs a container element
- Use proper methods: `setMode()`, `setIntensity()`, `setColors()`
- NOT `updateSettings()` - that doesn't exist!

### Values Not Transforming
- First value to LUT becomes center - make sure stick is centered
- In learning mode, LUT builds calibration over time
- Use calibrated mode for instant setup with known values

## 📝 Notes

- **HTTPS Required**: WebHID only works on secure contexts
- **Browser Support**: Chrome/Edge only (no Firefox/Safari)
- **Setup Mode**: Essential - normal mode won't work with these tools
- **Non-blocking Design**: All sensor reads are cached to prevent UI freezes
- **Event-Driven**: Subscribe to changes rather than polling for updates