Files
reterminal-dm4/archive/chromium-setup-legacy/LED-CONTROL-GUIDE.md

17 KiB

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

# 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

# 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

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

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

# 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)

# 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

# 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

# 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

#!/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

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:

sudo usermod -a -G audio pi
sudo udevadm control --reload-rules
sudo udevadm trigger

Use Cases

Status Indicator

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

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

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:

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:

    ls -la /sys/class/leds/usr-led/
    cat /sys/class/leds/usr-led/brightness
    
  2. Ensure trigger is set to 'none' for manual control:

    # 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:

    # 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:

    # 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):

    # 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.

  • 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