# reTerminal DM4 LED Control Guide

## Overview

The reTerminal DM4 has a user-controllable LED (`usr-led`) that can be used for status indicators, notifications, and visual feedback. The LED is controlled via the Linux LED subsystem, similar to the buzzer.

**⚠️ Important Note**: While the LED control interface exists and responds to commands, the physical LED may not be visible or installed on all reTerminal DM4 units. The control interface will accept commands and report correct values (0 for off, 255 for on), but if the LED doesn't appear to turn on physically, it may not be present on your specific model. Always verify with Seeed Studio documentation for your specific unit.

## LED Device Information

- **Device Path**: `/sys/class/leds/usr-led`
- **Control Method**: Brightness control (0 = off, 1 = on)
- **Type**: Simple on/off LED (max brightness = 1)
- **Location**: User-controllable status LED
- **Hardware**: Controlled via PCA9535 GPIO expander (I2C address 0x21)
- **Note**: When setting brightness to 1, the system may report 255 (this is normal - it represents max brightness)

## Available LEDs on reTerminal DM4

The device has multiple LEDs, but the user-controllable one is:

- **`usr-led`**: User-controllable LED (primary LED for applications)
- **`usr-buzzer`**: Buzzer (not an LED, but controlled similarly)
- **`ACT`**: Activity LED (system-controlled, shows SD card activity)
- **`PWR`**: Power LED (system-controlled)
- **`lcd-pwr`**: LCD power LED (system-controlled)
- **`audio-pwr`**: Audio power LED (system-controlled)

## Basic LED Control

### Simple On/Off

```bash
# Turn LED ON
echo 1 | sudo tee /sys/class/leds/usr-led/brightness

# Turn LED OFF
echo 0 | sudo tee /sys/class/leds/usr-led/brightness
```

### Check LED Status

```bash
# Check current brightness (0 = off, 1 = on)
cat /sys/class/leds/usr-led/brightness

# Check max brightness
cat /sys/class/leds/usr-led/max_brightness
```

## Python Control

### Simple Python Functions

```python
import subprocess

LED_PATH = '/sys/class/leds/usr-led/brightness'

def led_on():
    """Turn LED ON"""
    subprocess.run(['sudo', 'tee', LED_PATH], 
                  input='1', text=True, 
                  stdout=subprocess.DEVNULL, 
                  stderr=subprocess.DEVNULL)

def led_off():
    """Turn LED OFF"""
    subprocess.run(['sudo', 'tee', LED_PATH], 
                  input='0', text=True, 
                  stdout=subprocess.DEVNULL, 
                  stderr=subprocess.DEVNULL)

def led_toggle():
    """Toggle LED state"""
    with open(LED_PATH, 'r') as f:
        current = f.read().strip()
    new_state = '0' if current == '1' else '1'
    subprocess.run(['sudo', 'tee', LED_PATH], 
                  input=new_state, text=True, 
                  stdout=subprocess.DEVNULL, 
                  stderr=subprocess.DEVNULL)

def led_status():
    """Get LED status"""
    with open(LED_PATH, 'r') as f:
        return 'ON' if f.read().strip() == '1' else 'OFF'
```

### Blinking Pattern

```python
import time
import subprocess

LED_PATH = '/sys/class/leds/usr-led/brightness'

def led_blink(count=5, on_time=0.2, off_time=0.2):
    """Blink LED specified number of times"""
    for _ in range(count):
        subprocess.run(['sudo', 'tee', LED_PATH], 
                      input='1', text=True, 
                      stdout=subprocess.DEVNULL, 
                      stderr=subprocess.DEVNULL)
        time.sleep(on_time)
        subprocess.run(['sudo', 'tee', LED_PATH], 
                      input='0', text=True, 
                      stdout=subprocess.DEVNULL, 
                      stderr=subprocess.DEVNULL)
        time.sleep(off_time)

def led_pulse(duration=2, interval=0.1):
    """Pulse LED (rapid on/off) for specified duration"""
    end_time = time.time() + duration
    while time.time() < end_time:
        subprocess.run(['sudo', 'tee', LED_PATH], 
                      input='1', text=True, 
                      stdout=subprocess.DEVNULL, 
                      stderr=subprocess.DEVNULL)
        time.sleep(interval)
        subprocess.run(['sudo', 'tee', LED_PATH], 
                      input='0', text=True, 
                      stdout=subprocess.DEVNULL, 
                      stderr=subprocess.DEVNULL)
        time.sleep(interval)
```

## Using LED Triggers

The LED supports various triggers for automatic control:

### Available Triggers

```bash
# List available triggers
cat /sys/class/leds/usr-led/trigger
```

Common triggers:
- `none` - Manual control (default)
- `timer` - Blink at specified intervals
- `heartbeat` - Blink in heartbeat pattern
- `default-on` - Always on
- `cpu` - Blink based on CPU activity
- `mmc0` - Blink on SD card activity

### Timer Trigger (Blinking)

```bash
# Set timer trigger
echo timer | sudo tee /sys/class/leds/usr-led/trigger

# Set delay_on (time LED is ON in milliseconds)
echo 500 | sudo tee /sys/class/leds/usr-led/delay_on

# Set delay_off (time LED is OFF in milliseconds)
echo 500 | sudo tee /sys/class/leds/usr-led/delay_off

# Disable timer (return to manual control)
echo none | sudo tee /sys/class/leds/usr-led/trigger
```

### Heartbeat Trigger

```bash
# Set heartbeat pattern
echo heartbeat | sudo tee /sys/class/leds/usr-led/trigger

# Return to manual control
echo none | sudo tee /sys/class/leds/usr-led/trigger
```

### CPU Activity Trigger

```bash
# Blink based on CPU activity
echo cpu | sudo tee /sys/class/leds/usr-led/trigger

# Return to manual control
echo none | sudo tee /sys/class/leds/usr-led/trigger
```

## Bash Script Examples

### Simple LED Control Script

```bash
#!/bin/bash
# LED control script

LED_PATH='/sys/class/leds/usr-led/brightness'

case "$1" in
    on)
        echo 1 | sudo tee $LED_PATH > /dev/null
        echo "LED ON"
        ;;
    off)
        echo 0 | sudo tee $LED_PATH > /dev/null
        echo "LED OFF"
        ;;
    toggle)
        current=$(cat $LED_PATH)
        if [ "$current" = "1" ]; then
            echo 0 | sudo tee $LED_PATH > /dev/null
            echo "LED OFF"
        else
            echo 1 | sudo tee $LED_PATH > /dev/null
            echo "LED ON"
        fi
        ;;
    blink)
        count=${2:-5}
        for i in $(seq 1 $count); do
            echo 1 | sudo tee $LED_PATH > /dev/null
            sleep 0.2
            echo 0 | sudo tee $LED_PATH > /dev/null
            sleep 0.2
        done
        ;;
    status)
        current=$(cat $LED_PATH)
        echo "LED is: $([ $current -eq 1 ] && echo 'ON' || echo 'OFF')"
        ;;
    *)
        echo "Usage: $0 {on|off|toggle|blink [count]|status}"
        exit 1
        ;;
esac
```

## Flask Integration

### Add LED Control to Flask App

```python
from flask import Flask, jsonify, request
import subprocess
import threading
import time

app = Flask(__name__)
LED_PATH = '/sys/class/leds/usr-led/brightness'

class LEDController:
    def __init__(self):
        self.is_blinking = False
        self.blink_thread = None
    
    def _write_led(self, value):
        """Internal method to write to LED"""
        try:
            subprocess.run(['sudo', 'tee', LED_PATH], 
                          input=str(value), text=True, 
                          stdout=subprocess.DEVNULL, 
                          stderr=subprocess.DEVNULL,
                          check=True)
            return True
        except subprocess.CalledProcessError:
            return False
    
    def on(self):
        """Turn LED ON"""
        return self._write_led(1)
    
    def off(self):
        """Turn LED OFF"""
        return self._write_led(0)
    
    def toggle(self):
        """Toggle LED state"""
        try:
            result = subprocess.run(['cat', LED_PATH], 
                                 capture_output=True, text=True, check=True)
            current = result.stdout.strip()
            new_state = '0' if current == '1' else '1'
            return self._write_led(new_state)
        except:
            return False
    
    def blink(self, count=5, on_time=0.2, off_time=0.2):
        """Blink LED specified number of times"""
        if self.is_blinking:
            return False
        
        def _blink():
            self.is_blinking = True
            for _ in range(count):
                self.on()
                time.sleep(on_time)
                self.off()
                time.sleep(off_time)
            self.is_blinking = False
        
        self.blink_thread = threading.Thread(target=_blink, daemon=True)
        self.blink_thread.start()
        return True
    
    def pulse(self, duration=2, interval=0.1):
        """Pulse LED for specified duration"""
        if self.is_blinking:
            return False
        
        def _pulse():
            self.is_blinking = True
            end_time = time.time() + duration
            while time.time() < end_time:
                self.on()
                time.sleep(interval)
                self.off()
                time.sleep(interval)
            self.is_blinking = False
        
        self.blink_thread = threading.Thread(target=_pulse, daemon=True)
        self.blink_thread.start()
        return True
    
    def status(self):
        """Get LED status"""
        try:
            result = subprocess.run(['cat', LED_PATH], 
                                  capture_output=True, text=True, check=True)
            state = result.stdout.strip()
            return 'ON' if state == '1' else 'OFF'
        except:
            return 'UNKNOWN'

led = LEDController()

@app.route('/led/on', methods=['POST', 'GET'])
def led_on():
    if led.on():
        return jsonify({'status': 'success', 'message': 'LED turned ON'})
    return jsonify({'status': 'error', 'message': 'Failed to turn LED ON'}), 500

@app.route('/led/off', methods=['POST', 'GET'])
def led_off():
    if led.off():
        return jsonify({'status': 'success', 'message': 'LED turned OFF'})
    return jsonify({'status': 'error', 'message': 'Failed to turn LED OFF'}), 500

@app.route('/led/toggle', methods=['POST', 'GET'])
def led_toggle():
    if led.toggle():
        return jsonify({'status': 'success', 'message': 'LED toggled'})
    return jsonify({'status': 'error', 'message': 'Failed to toggle LED'}), 500

@app.route('/led/blink', methods=['POST', 'GET'])
def led_blink():
    count = int(request.args.get('count', 5))
    on_time = float(request.args.get('on_time', 0.2))
    off_time = float(request.args.get('off_time', 0.2))
    
    if led.blink(count, on_time, off_time):
        return jsonify({
            'status': 'success', 
            'message': f'LED blinking {count} times'
        })
    return jsonify({
        'status': 'error', 
        'message': 'LED is already blinking'
    }), 409

@app.route('/led/pulse', methods=['POST', 'GET'])
def led_pulse():
    duration = float(request.args.get('duration', 2))
    interval = float(request.args.get('interval', 0.1))
    
    if led.pulse(duration, interval):
        return jsonify({
            'status': 'success', 
            'message': f'LED pulsing for {duration} seconds'
        })
    return jsonify({
        'status': 'error', 
        'message': 'LED is already active'
    }), 409

@app.route('/led/status', methods=['GET'])
def led_status():
    state = led.status()
    return jsonify({
        'status': 'success',
        'led': state,
        'is_blinking': led.is_blinking
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)
```

## Permission Setup

Same as buzzer control - see `FLASK-BUZZER-CONTROL.md` for detailed permission setup options.

### Quick Setup (sudoers method)

Create `/etc/sudoers.d/led-control`:

```
# Allow LED control without password
pi ALL=(ALL) NOPASSWD: /usr/bin/tee /sys/class/leds/usr-led/brightness
```

### Udev Rules Method

Create `/etc/udev/rules.d/99-led.rules`:

```
SUBSYSTEM=="leds", KERNEL=="usr-led", MODE="0666", GROUP="audio"
```

Then add user to audio group and reload udev:
```bash
sudo usermod -a -G audio pi
sudo udevadm control --reload-rules
sudo udevadm trigger
```

## Use Cases

### Status Indicator

```python
def show_status(status):
    """Show status with LED"""
    if status == 'success':
        led.on()
        time.sleep(0.5)
        led.off()
    elif status == 'error':
        led.blink(3, 0.1, 0.1)  # Fast blink 3 times
    elif status == 'warning':
        led.blink(2, 0.3, 0.3)  # Slow blink 2 times
```

### Notification System

```python
def notify(message_type):
    """Notify user with LED patterns"""
    patterns = {
        'info': (1, 0.2, 0.2),      # 1 blink, 0.2s on/off
        'success': (2, 0.1, 0.1),    # 2 fast blinks
        'error': (3, 0.05, 0.05),    # 3 very fast blinks
        'warning': (2, 0.3, 0.3),    # 2 slow blinks
    }
    
    count, on_time, off_time = patterns.get(message_type, (1, 0.2, 0.2))
    led.blink(count, on_time, off_time)
```

### System Monitoring

```python
import psutil

def monitor_system():
    """Use LED to indicate system load"""
    cpu_percent = psutil.cpu_percent(interval=1)
    
    if cpu_percent > 80:
        led.pulse(1, 0.05)  # Fast pulse for high load
    elif cpu_percent > 50:
        led.pulse(1, 0.1)   # Medium pulse
    else:
        led.off()           # Off for low load
```

## Combined LED and Buzzer Control

You can combine LED and buzzer for multi-modal notifications:

```python
def alert(message_type):
    """Alert with both LED and buzzer"""
    if message_type == 'critical':
        # Fast LED blink + buzzer beep
        threading.Thread(target=led.blink, args=(5, 0.1, 0.1), daemon=True).start()
        buzzer.beep(0.3)
    elif message_type == 'notification':
        # Slow LED blink
        threading.Thread(target=led.blink, args=(2, 0.3, 0.3), daemon=True).start()
```

## Troubleshooting

### LED Not Responding / Not Visible

**Important**: The LED control interface accepts commands, but the LED may not be physically visible or may be in a location that's not easily observable. Here are troubleshooting steps:

1. **Verify LED device exists:**
   ```bash
   ls -la /sys/class/leds/usr-led/
   cat /sys/class/leds/usr-led/brightness
   ```

2. **Ensure trigger is set to 'none' for manual control:**
   ```bash
   # Check current trigger
   cat /sys/class/leds/usr-led/trigger
   
   # Set to 'none' for manual control
   echo none | sudo tee /sys/class/leds/usr-led/trigger
   ```

3. **Test with different values:**
   ```bash
   # Turn OFF
   echo 0 | sudo tee /sys/class/leds/usr-led/brightness
   sleep 1
   
   # Turn ON (may show as 255, which is normal)
   echo 1 | sudo tee /sys/class/leds/usr-led/brightness
   sleep 1
   
   # Check what value was actually set
   cat /sys/class/leds/usr-led/brightness
   ```

4. **Check if LED responds to trigger modes:**
   ```bash
   # Try default-on trigger
   echo default-on | sudo tee /sys/class/leds/usr-led/trigger
   sleep 2
   
   # Return to manual control
   echo none | sudo tee /sys/class/leds/usr-led/trigger
   ```

5. **Physical verification:**
   - The LED may be located in a position that's not easily visible
   - Check around the screen bezel, especially near the buzzer location
   - The LED might be very dim or require specific viewing angle
   - Some reTerminal DM units may not have a physical LED installed

6. **Check GPIO control (alternative method):**
   ```bash
   # The LED is controlled via PCA9535 GPIO expander
   # Base GPIO: 578
   # You can try controlling via GPIO directly if LED subsystem doesn't work
   ```

### LED Always On/Off

- Check if a trigger is active: `cat /sys/class/leds/usr-led/trigger`
- Set trigger to 'none' for manual control: `echo none | sudo tee /sys/class/leds/usr-led/trigger`
- Verify brightness value: `cat /sys/class/leds/usr-led/brightness` (should be 0 for off, 255 for on)

### Brightness Shows 255 Instead of 1

**This is normal behavior!** When you set brightness to 1 (on), the system may report it as 255. This is because:
- The LED subsystem internally uses 0-255 range
- Setting 1 maps to maximum brightness (255)
- This is expected behavior and doesn't indicate a problem

### LED Not Physically Visible

According to official documentation, the reTerminal DM may not have a user-controllable LED indicator in all models. If the LED doesn't appear to turn on:

1. **Verify your model**: Check if your specific reTerminal DM4 unit includes a user LED
2. **Check documentation**: Refer to Seeed Studio's official documentation for your specific model
3. **Contact support**: If LED control is required, contact Seeed Studio support for model-specific information

### Alternative: Use System LEDs

If `usr-led` is not available or not visible, you can use system LEDs for status indication:
- **ACT LED**: Shows SD card activity (system-controlled)
- **PWR LED**: Power indicator (system-controlled)

Note: System LEDs are typically read-only and controlled by the system.

## Related Documentation

- See `FLASK-BUZZER-CONTROL.md` for Flask integration examples
- See `BUZZER-TEST-GUIDE.md` for buzzer control
- See `AUDIO-CONFIGURATION-REPORT.md` for complete hardware info
