Files
reterminal-dm4/archive/chromium-setup-legacy/FLASK-BUZZER-CONTROL.md

18 KiB

Flask Buzzer Control Documentation

Overview

This guide explains how to control the reTerminal DM4 buzzer from a Python Flask web application. The buzzer is accessed via the Linux sysfs interface at /sys/class/leds/usr-buzzer/brightness.

Prerequisites

  • Python 3.x installed
  • Flask installed (pip install flask)
  • sudo permissions (or user in appropriate groups)
  • reTerminal DM4 with buzzer accessible

Basic Buzzer Control Functions

Simple Python Buzzer Control

import os

BUZZER_PATH = '/sys/class/leds/usr-buzzer/brightness'

def buzzer_on():
    """Turn buzzer ON"""
    with open(BUZZER_PATH, 'w') as f:
        f.write('1')

def buzzer_off():
    """Turn buzzer OFF"""
    with open(BUZZER_PATH, 'w') as f:
        f.write('0')

def buzzer_beep(duration=0.2):
    """Play a beep for specified duration (in seconds)"""
    import time
    buzzer_on()
    time.sleep(duration)
    buzzer_off()

Note: These functions require root privileges. See "Permission Setup" section below.

Flask Application Examples

Example 1: Basic Flask App with Buzzer Control

#!/usr/bin/env python3
from flask import Flask, jsonify, request
import os
import time
import subprocess

app = Flask(__name__)
BUZZER_PATH = '/sys/class/leds/usr-buzzer/brightness'

def buzzer_on():
    """Turn buzzer ON"""
    try:
        subprocess.run(['sudo', 'tee', BUZZER_PATH], 
                      input='1', text=True, 
                      stdout=subprocess.DEVNULL, 
                      stderr=subprocess.DEVNULL,
                      check=True)
        return True
    except subprocess.CalledProcessError:
        return False

def buzzer_off():
    """Turn buzzer OFF"""
    try:
        subprocess.run(['sudo', 'tee', BUZZER_PATH], 
                      input='0', text=True, 
                      stdout=subprocess.DEVNULL, 
                      stderr=subprocess.DEVNULL,
                      check=True)
        return True
    except subprocess.CalledProcessError:
        return False

def buzzer_beep(duration=0.2):
    """Play a beep for specified duration"""
    buzzer_on()
    time.sleep(duration)
    buzzer_off()

@app.route('/')
def index():
    return jsonify({
        'status': 'Buzzer Control API',
        'endpoints': {
            '/buzzer/on': 'Turn buzzer ON',
            '/buzzer/off': 'Turn buzzer OFF',
            '/buzzer/beep': 'Play a beep (duration parameter)',
            '/buzzer/status': 'Get buzzer status'
        }
    })

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

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

@app.route('/buzzer/beep', methods=['POST', 'GET'])
def beep():
    duration = float(request.args.get('duration', 0.2))
    if duration < 0 or duration > 5:
        return jsonify({'status': 'error', 'message': 'Duration must be between 0 and 5 seconds'}), 400
    
    buzzer_beep(duration)
    return jsonify({'status': 'success', 'message': f'Beeped for {duration} seconds'})

@app.route('/buzzer/status', methods=['GET'])
def status():
    try:
        with open(BUZZER_PATH, 'r') as f:
            state = f.read().strip()
        return jsonify({
            'status': 'success',
            'buzzer': 'ON' if state == '1' else 'OFF',
            'state': state
        })
    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

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

Example 2: Advanced Flask App with Pattern Support

#!/usr/bin/env python3
from flask import Flask, jsonify, request
import subprocess
import time
import threading

app = Flask(__name__)
BUZZER_PATH = '/sys/class/leds/usr-buzzer/brightness'

class BuzzerController:
    def __init__(self):
        self.is_playing = False
        self.play_thread = None
    
    def _write_buzzer(self, value):
        """Internal method to write to buzzer"""
        try:
            subprocess.run(['sudo', 'tee', BUZZER_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 buzzer ON"""
        return self._write_buzzer(1)
    
    def off(self):
        """Turn buzzer OFF"""
        return self._write_buzzer(0)
    
    def beep(self, duration=0.2):
        """Play a single beep"""
        self.on()
        time.sleep(duration)
        self.off()
    
    def play_pattern(self, pattern):
        """
        Play a pattern
        pattern: list of tuples [(duration_on, duration_off), ...]
        Example: [(0.1, 0.1), (0.1, 0.1), (0.1, 0.3)] = two short beeps, pause, one beep
        """
        if self.is_playing:
            return False
        
        def _play():
            self.is_playing = True
            for on_time, off_time in pattern:
                self.on()
                time.sleep(on_time)
                self.off()
                time.sleep(off_time)
            self.is_playing = False
        
        self.play_thread = threading.Thread(target=_play, daemon=True)
        self.play_thread.start()
        return True
    
    def starwars_theme(self, duration=5):
        """Play Star Wars theme pattern"""
        if self.is_playing:
            return False
        
        def _play():
            self.is_playing = True
            start_time = time.time()
            
            # Opening sequence
            self.beep(0.05)
            time.sleep(0.05)
            self.beep(0.05)
            time.sleep(0.05)
            self.beep(0.05)
            time.sleep(0.1)
            self.beep(0.1)
            time.sleep(0.1)
            self.beep(0.15)
            time.sleep(0.15)
            
            # Continue pattern until duration reached
            while time.time() - start_time < duration:
                self.beep(0.08)
                time.sleep(0.05)
                self.beep(0.08)
                time.sleep(0.05)
                self.beep(0.08)
                time.sleep(0.1)
                self.beep(0.12)
                time.sleep(0.1)
            
            self.off()
            self.is_playing = False
        
        self.play_thread = threading.Thread(target=_play, daemon=True)
        self.play_thread.start()
        return True

# Create global buzzer controller
buzzer = BuzzerController()

@app.route('/')
def index():
    return jsonify({
        'status': 'Advanced Buzzer Control API',
        'endpoints': {
            '/buzzer/on': 'Turn buzzer ON (POST/GET)',
            '/buzzer/off': 'Turn buzzer OFF (POST/GET)',
            '/buzzer/beep': 'Play a beep (GET: ?duration=0.2)',
            '/buzzer/pattern': 'Play custom pattern (POST: JSON)',
            '/buzzer/starwars': 'Play Star Wars theme (GET: ?duration=5)',
            '/buzzer/status': 'Get buzzer status (GET)'
        }
    })

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

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

@app.route('/buzzer/beep', methods=['POST', 'GET'])
def beep():
    duration = float(request.args.get('duration', 0.2))
    if duration < 0 or duration > 5:
        return jsonify({'status': 'error', 'message': 'Duration must be between 0 and 5 seconds'}), 400
    
    buzzer.beep(duration)
    return jsonify({'status': 'success', 'message': f'Beeped for {duration} seconds'})

@app.route('/buzzer/pattern', methods=['POST'])
def play_pattern():
    data = request.get_json()
    if not data or 'pattern' not in data:
        return jsonify({'status': 'error', 'message': 'Pattern required in JSON body'}), 400
    
    pattern = data['pattern']
    if not isinstance(pattern, list):
        return jsonify({'status': 'error', 'message': 'Pattern must be a list'}), 400
    
    # Validate pattern format
    try:
        validated_pattern = []
        for item in pattern:
            if isinstance(item, list) and len(item) == 2:
                validated_pattern.append((float(item[0]), float(item[1])))
            else:
                return jsonify({'status': 'error', 'message': 'Each pattern item must be [on_time, off_time]'}), 400
    except (ValueError, TypeError):
        return jsonify({'status': 'error', 'message': 'Invalid pattern format'}), 400
    
    if buzzer.play_pattern(validated_pattern):
        return jsonify({'status': 'success', 'message': 'Pattern playing'})
    return jsonify({'status': 'error', 'message': 'Buzzer is already playing'}), 409

@app.route('/buzzer/starwars', methods=['GET'])
def starwars():
    duration = float(request.args.get('duration', 5))
    if duration < 0 or duration > 10:
        return jsonify({'status': 'error', 'message': 'Duration must be between 0 and 10 seconds'}), 400
    
    if buzzer.starwars_theme(duration):
        return jsonify({'status': 'success', 'message': f'Playing Star Wars theme for {duration} seconds'})
    return jsonify({'status': 'error', 'message': 'Buzzer is already playing'}), 409

@app.route('/buzzer/status', methods=['GET'])
def status():
    try:
        result = subprocess.run(['cat', BUZZER_PATH], 
                              capture_output=True, text=True, check=True)
        state = result.stdout.strip()
        return jsonify({
            'status': 'success',
            'buzzer': 'ON' if state == '1' else 'OFF',
            'state': state,
            'is_playing': buzzer.is_playing
        })
    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

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

Permission Setup

sudo python3 app.py

Create a file /etc/sudoers.d/buzzer-control:

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

Then your Flask app can use sudo without password prompts.

Option 3: Use udev rules (Most Secure)

Create /etc/udev/rules.d/99-buzzer.rules:

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

Add your user to the audio group:

sudo usermod -a -G audio pi

Then reload udev:

sudo udevadm control --reload-rules
sudo udevadm trigger

After this, you can write directly without sudo:

def buzzer_on():
    with open(BUZZER_PATH, 'w') as f:
        f.write('1')

API Usage Examples

Using curl

# Turn buzzer ON
curl http://localhost:5000/buzzer/on

# Turn buzzer OFF
curl http://localhost:5000/buzzer/off

# Play a beep (0.5 seconds)
curl "http://localhost:5000/buzzer/beep?duration=0.5"

# Play Star Wars theme (5 seconds)
curl "http://localhost:5000/buzzer/starwars?duration=5"

# Play custom pattern
curl -X POST http://localhost:5000/buzzer/pattern \
  -H "Content-Type: application/json" \
  -d '{"pattern": [[0.1, 0.1], [0.1, 0.1], [0.1, 0.3], [0.2, 0.2]]}'

# Check status
curl http://localhost:5000/buzzer/status

Using Python requests

import requests

base_url = "http://localhost:5000"

# Turn on
response = requests.post(f"{base_url}/buzzer/on")
print(response.json())

# Play beep
response = requests.get(f"{base_url}/buzzer/beep", params={"duration": 0.3})
print(response.json())

# Play pattern
pattern = [[0.1, 0.1], [0.1, 0.1], [0.1, 0.3]]
response = requests.post(f"{base_url}/buzzer/pattern", json={"pattern": pattern})
print(response.json())

# Check status
response = requests.get(f"{base_url}/buzzer/status")
print(response.json())

Using JavaScript/Fetch

// Turn buzzer ON
fetch('http://localhost:5000/buzzer/on', {method: 'POST'})
  .then(res => res.json())
  .then(data => console.log(data));

// Play beep
fetch('http://localhost:5000/buzzer/beep?duration=0.5')
  .then(res => res.json())
  .then(data => console.log(data));

// Play Star Wars theme
fetch('http://localhost:5000/buzzer/starwars?duration=5')
  .then(res => res.json())
  .then(data => console.log(data));

// Play custom pattern
fetch('http://localhost:5000/buzzer/pattern', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({
    pattern: [[0.1, 0.1], [0.1, 0.1], [0.1, 0.3], [0.2, 0.2]]
  })
})
  .then(res => res.json())
  .then(data => console.log(data));

Web Interface Example

HTML/JavaScript Frontend

<!DOCTYPE html>
<html>
<head>
    <title>Buzzer Control</title>
    <style>
        body { font-family: Arial; padding: 20px; }
        button { padding: 10px 20px; margin: 5px; font-size: 16px; }
        .status { margin-top: 20px; padding: 10px; background: #f0f0f0; }
    </style>
</head>
<body>
    <h1>reTerminal DM4 Buzzer Control</h1>
    
    <div>
        <button onclick="buzzerOn()">Buzzer ON</button>
        <button onclick="buzzerOff()">Buzzer OFF</button>
        <button onclick="beep(0.2)">Short Beep</button>
        <button onclick="beep(0.5)">Long Beep</button>
        <button onclick="starwars()">Star Wars Theme</button>
    </div>
    
    <div>
        <h3>Custom Pattern</h3>
        <input type="text" id="pattern" placeholder="0.1,0.1,0.1,0.1,0.1,0.3" style="width: 300px;">
        <button onclick="playPattern()">Play Pattern</button>
    </div>
    
    <div class="status" id="status">Ready</div>
    
    <script>
        const API_URL = 'http://localhost:5000';
        
        function updateStatus(message) {
            document.getElementById('status').textContent = message;
        }
        
        async function buzzerOn() {
            const response = await fetch(`${API_URL}/buzzer/on`, {method: 'POST'});
            const data = await response.json();
            updateStatus(data.message);
        }
        
        async function buzzerOff() {
            const response = await fetch(`${API_URL}/buzzer/off`, {method: 'POST'});
            const data = await response.json();
            updateStatus(data.message);
        }
        
        async function beep(duration) {
            const response = await fetch(`${API_URL}/buzzer/beep?duration=${duration}`);
            const data = await response.json();
            updateStatus(data.message);
        }
        
        async function starwars() {
            const response = await fetch(`${API_URL}/buzzer/starwars?duration=5`);
            const data = await response.json();
            updateStatus(data.message);
        }
        
        async function playPattern() {
            const patternStr = document.getElementById('pattern').value;
            const parts = patternStr.split(',');
            const pattern = [];
            for (let i = 0; i < parts.length; i += 2) {
                if (i + 1 < parts.length) {
                    pattern.push([parseFloat(parts[i]), parseFloat(parts[i+1])]);
                }
            }
            
            const response = await fetch(`${API_URL}/buzzer/pattern`, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({pattern: pattern})
            });
            const data = await response.json();
            updateStatus(data.message);
        }
    </script>
</body>
</html>

Security Considerations

  1. Authentication: Add authentication for production use:

    from flask_httpauth import HTTPBasicAuth
    auth = HTTPBasicAuth()
    
    @auth.verify_password
    def verify_password(username, password):
        return username == 'admin' and password == 'secret'
    
    @app.route('/buzzer/on')
    @auth.login_required
    def turn_on():
        # ...
    
  2. Rate Limiting: Prevent abuse:

    from flask_limiter import Limiter
    limiter = Limiter(app, key_func=get_remote_address)
    
    @app.route('/buzzer/beep')
    @limiter.limit("10 per minute")
    def beep():
        # ...
    
  3. CORS: If accessing from web pages:

    from flask_cors import CORS
    CORS(app)
    

Installation and Setup

  1. Install Flask:

    pip3 install flask
    # For advanced features:
    pip3 install flask-httpauth flask-limiter flask-cors
    
  2. Set up permissions (choose one method from above)

  3. Run the application:

    python3 app.py
    
  4. Run as a service (systemd): Create /etc/systemd/system/buzzer-api.service:

    [Unit]
    Description=Buzzer Control API
    After=network.target
    
    [Service]
    Type=simple
    User=pi
    WorkingDirectory=/home/pi/buzzer-api
    ExecStart=/usr/bin/python3 /home/pi/buzzer-api/app.py
    Restart=always
    
    [Install]
    WantedBy=multi-user.target
    

    Enable and start:

    sudo systemctl enable buzzer-api
    sudo systemctl start buzzer-api
    

Troubleshooting

Permission Denied

  • Check if user has sudo access
  • Verify udev rules are applied
  • Check file permissions: ls -la /sys/class/leds/usr-buzzer/brightness

Buzzer Not Responding

  • Verify buzzer device exists: ls /sys/class/leds/usr-buzzer/
  • Test manually: echo 1 | sudo tee /sys/class/leds/usr-buzzer/brightness
  • Check kernel messages: dmesg | grep buzzer

Flask App Not Starting

  • Check if port 5000 is in use: sudo netstat -tulpn | grep 5000
  • Verify Python and Flask are installed: python3 --version && pip3 list | grep Flask

Complete Example Project Structure

buzzer-api/
├── app.py              # Main Flask application
├── buzzer.py           # Buzzer control module
├── requirements.txt    # Python dependencies
├── static/            # Static files (CSS, JS)
│   └── index.html     # Web interface
└── README.md          # Project documentation

requirements.txt:

Flask==2.3.0
flask-httpauth==4.8.0
flask-limiter==3.0.0
flask-cors==4.0.0
  • See BUZZER-TEST-GUIDE.md for basic buzzer testing
  • See AUDIO-CONFIGURATION-REPORT.md for complete audio system info