Implement automatic page scaling feature with viewport adjustments

This commit is contained in:
nearxos
2026-02-18 09:33:44 +02:00
parent a9b3726ace
commit d6b09cdd6f
35 changed files with 5722 additions and 0 deletions

View File

@@ -0,0 +1,660 @@
# 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