# 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