# 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