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 intervalsheartbeat- Blink in heartbeat patterndefault-on- Always oncpu- Blink based on CPU activitymmc0- 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:
-
Verify LED device exists:
ls -la /sys/class/leds/usr-led/ cat /sys/class/leds/usr-led/brightness -
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 -
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 -
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 -
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
-
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:
- Verify your model: Check if your specific reTerminal DM4 unit includes a user LED
- Check documentation: Refer to Seeed Studio's official documentation for your specific model
- 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.mdfor Flask integration examples - See
BUZZER-TEST-GUIDE.mdfor buzzer control - See
AUDIO-CONFIGURATION-REPORT.mdfor complete hardware info