# 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

```python
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

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

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

### Option 1: Run Flask with sudo (Not Recommended for Production)

```bash
sudo python3 app.py
```

### Option 2: Configure sudoers for passwordless access (Recommended)

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:
```bash
sudo usermod -a -G audio pi
```

Then reload udev:
```bash
sudo udevadm control --reload-rules
sudo udevadm trigger
```

After this, you can write directly without sudo:
```python
def buzzer_on():
    with open(BUZZER_PATH, 'w') as f:
        f.write('1')
```

## API Usage Examples

### Using curl

```bash
# 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

```python
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

```javascript
// 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

```html
<!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:
   ```python
   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:
   ```python
   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:
   ```python
   from flask_cors import CORS
   CORS(app)
   ```

## Installation and Setup

1. **Install Flask:**
   ```bash
   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:**
   ```bash
   python3 app.py
   ```

4. **Run as a service** (systemd):
   Create `/etc/systemd/system/buzzer-api.service`:
   ```ini
   [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:
   ```bash
   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
```

## Related Documentation

- See `BUZZER-TEST-GUIDE.md` for basic buzzer testing
- See `AUDIO-CONFIGURATION-REPORT.md` for complete audio system info
