661 lines
18 KiB
Markdown
661 lines
18 KiB
Markdown
# 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
|