561 lines
14 KiB
Markdown
561 lines
14 KiB
Markdown
# ESP32 Bluetooth Relay Integration Guide
|
|
|
|
Complete guide for integrating ESP32 8-channel Bluetooth relay controller with the Moonlight Drive-In Theatre system.
|
|
|
|
## 🎯 Overview
|
|
|
|
This integration allows the theatre software to automatically control:
|
|
- **House lights** - Main theatre lighting
|
|
- **Screen lights** - Illumination around the screen
|
|
- **Marquee lights** - External entrance/sign lighting
|
|
- **Speaker power** - Audio system power
|
|
- **Projector power** - Video projector control
|
|
- **Effect lights** - Special effect lighting
|
|
- **Concession lights** - Snack bar area
|
|
- **Parking lights** - Parking area illumination
|
|
|
|
## 📦 Prerequisites
|
|
|
|
### Hardware Required
|
|
|
|
1. **ESP32 Development Board** (with Bluetooth)
|
|
2. **8-Channel Relay Module** (5V)
|
|
3. **Wiring components** (jumper wires, power supply)
|
|
4. **Computer with Bluetooth** (for Python script communication)
|
|
|
|
### Software Required
|
|
|
|
```bash
|
|
pip install pyserial
|
|
```
|
|
|
|
The ESP32 firmware is in the separate `esp32-bluetooth-relay` repository.
|
|
|
|
## 🔧 Hardware Setup
|
|
|
|
### Step 1: Build ESP32 Relay Controller
|
|
|
|
Follow the complete guide in the `esp32-bluetooth-relay` repository:
|
|
- Wire ESP32 to relay module
|
|
- Upload firmware to ESP32
|
|
- Test basic relay operation
|
|
|
|
### Step 2: Connect Loads to Relays
|
|
|
|
Map your theatre equipment to relays (default configuration):
|
|
|
|
| Relay | Function | Theatre Equipment |
|
|
|-------|----------|-------------------|
|
|
| 1 | House Lights | Main overhead lights |
|
|
| 2 | Screen Lights | Screen border lighting |
|
|
| 3 | Marquee Lights | Entrance sign |
|
|
| 4 | Speaker Power | Audio amplifier |
|
|
| 5 | Projector Power | Video projector |
|
|
| 6 | Effect Lights | RGB/effect lighting |
|
|
| 7 | Concession Lights | Snack bar |
|
|
| 8 | Parking Lights | Outdoor parking |
|
|
|
|
**⚠️ SAFETY:** Only qualified electricians should wire AC-powered equipment. Start with low-voltage DC loads for testing.
|
|
|
|
### Step 3: Pair ESP32 with Computer
|
|
|
|
**Windows:**
|
|
1. Open Bluetooth settings
|
|
2. Search for "ESP32-Relay-8CH"
|
|
3. Pair the device
|
|
4. Note the COM port assigned (e.g., COM4)
|
|
|
|
**Linux:**
|
|
```bash
|
|
# Scan for ESP32
|
|
hcitool scan
|
|
|
|
# Pair with ESP32 (replace XX:XX... with actual MAC)
|
|
bluetoothctl
|
|
> pair XX:XX:XX:XX:XX:XX
|
|
> trust XX:XX:XX:XX:XX:XX
|
|
> connect XX:XX:XX:XX:XX:XX
|
|
|
|
# Bind to serial port
|
|
sudo rfcomm bind /dev/rfcomm0 XX:XX:XX:XX:XX:XX
|
|
```
|
|
|
|
## 🚀 Software Integration
|
|
|
|
### Step 1: Install Python Dependencies
|
|
|
|
```bash
|
|
cd moonlight-drive-in
|
|
pip install pyserial
|
|
```
|
|
|
|
### Step 2: Configure Theatre Automation
|
|
|
|
Edit `config.py` and add the relay configuration section:
|
|
|
|
```python
|
|
# ESP32 Relay Controller Configuration
|
|
RELAY_CONFIG = {
|
|
# Enable/disable features
|
|
'enabled': True,
|
|
'auto_theatre_lights': True, # Auto dim lights during movies
|
|
'auto_speaker_power': True, # Auto power speakers
|
|
'light_dim_delay': 5, # Seconds before dimming
|
|
|
|
# Serial port (None = auto-detect, or specify like 'COM4' or '/dev/rfcomm0')
|
|
'relay_port': None,
|
|
|
|
# Custom relay mapping (optional - override defaults)
|
|
'relay_mapping': {
|
|
'HOUSE_LIGHTS': 1,
|
|
'SCREEN_LIGHTS': 2,
|
|
'MARQUEE_LIGHTS': 3,
|
|
'SPEAKER_POWER': 4,
|
|
'PROJECTOR_POWER': 5,
|
|
'EFFECT_LIGHTS': 6,
|
|
'CONCESSION_LIGHTS': 7,
|
|
'PARKING_LIGHTS': 8,
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 3: Modify MPV Player Integration
|
|
|
|
Edit `mpv_seamless_player.py` to add automation hooks:
|
|
|
|
```python
|
|
# At top of file, add imports
|
|
from theatre_automation import TheatreAutomation, RelayFunction
|
|
import config
|
|
|
|
# In __init__ method, add:
|
|
self.theatre = None
|
|
if config.RELAY_CONFIG.get('enabled', False):
|
|
try:
|
|
self.theatre = TheatreAutomation(config.RELAY_CONFIG)
|
|
if self.theatre.connect():
|
|
logger.info("Theatre automation enabled")
|
|
else:
|
|
logger.warning("Failed to connect to theatre automation")
|
|
self.theatre = None
|
|
except Exception as e:
|
|
logger.error(f"Theatre automation error: {e}")
|
|
self.theatre = None
|
|
|
|
# Before playing trailer, add:
|
|
if self.theatre:
|
|
self.theatre.movie_starting(is_trailer=True)
|
|
|
|
# After trailer ends, add:
|
|
if self.theatre:
|
|
self.theatre.movie_ended(is_trailer=True)
|
|
|
|
# Before playing main movie, add:
|
|
if self.theatre:
|
|
self.theatre.movie_starting(is_trailer=False)
|
|
|
|
# After movie ends, add:
|
|
if self.theatre:
|
|
self.theatre.movie_ended(is_trailer=False)
|
|
|
|
# On shutdown/cleanup, add:
|
|
if self.theatre:
|
|
self.theatre.disconnect()
|
|
```
|
|
|
|
### Step 4: Add Web Interface Controls
|
|
|
|
Edit `web_interface.py` to add relay control routes:
|
|
|
|
```python
|
|
from theatre_automation import TheatreAutomation, RelayFunction
|
|
|
|
# Add global theatre variable
|
|
theatre_automation = None
|
|
|
|
# Initialize in app startup
|
|
if config.RELAY_CONFIG.get('enabled'):
|
|
theatre_automation = TheatreAutomation(config.RELAY_CONFIG)
|
|
theatre_automation.connect()
|
|
|
|
# Add API endpoints
|
|
@app.route('/api/relay/status')
|
|
def relay_status():
|
|
"""Get status of all relays"""
|
|
if theatre_automation and theatre_automation.connected:
|
|
return jsonify(theatre_automation.get_status())
|
|
return jsonify({'error': 'Theatre automation not connected'}), 503
|
|
|
|
@app.route('/api/relay/<function_name>/<state>')
|
|
def relay_control(function_name, state):
|
|
"""Control a specific relay"""
|
|
if not theatre_automation or not theatre_automation.connected:
|
|
return jsonify({'error': 'Not connected'}), 503
|
|
|
|
try:
|
|
function = RelayFunction[function_name.upper()]
|
|
state_bool = state.lower() == 'on'
|
|
|
|
if theatre_automation.set_relay(function, state_bool):
|
|
return jsonify({'success': True})
|
|
else:
|
|
return jsonify({'error': 'Command failed'}), 500
|
|
except KeyError:
|
|
return jsonify({'error': 'Invalid function'}), 400
|
|
|
|
@app.route('/api/relay/effect/<effect_name>')
|
|
def relay_effect(effect_name):
|
|
"""Run a lighting effect"""
|
|
if theatre_automation and theatre_automation.connected:
|
|
theatre_automation.effect_sequence(effect_name)
|
|
return jsonify({'success': True})
|
|
return jsonify({'error': 'Not connected'}), 503
|
|
```
|
|
|
|
## 🧪 Testing
|
|
|
|
### Test 1: Basic Connection
|
|
|
|
```bash
|
|
python esp32_relay_controller.py
|
|
```
|
|
|
|
This will:
|
|
- List available serial ports
|
|
- Connect to ESP32
|
|
- Run interactive test mode
|
|
|
|
### Test 2: Theatre Automation
|
|
|
|
```bash
|
|
python theatre_automation.py
|
|
```
|
|
|
|
This will:
|
|
- Initialize theatre automation
|
|
- Run a demo sequence (trailer → movie → closing)
|
|
- Test all automation functions
|
|
|
|
### Test 3: Integration Test
|
|
|
|
1. Start the theatre software normally
|
|
2. Watch for log messages: "Theatre automation enabled"
|
|
3. Scan an NFC tag to start a movie
|
|
4. Observe lights dimming after delay
|
|
5. Movie ends and lights come back up
|
|
|
|
## 🎮 Manual Control
|
|
|
|
### Command Line Control
|
|
|
|
```python
|
|
from theatre_automation import TheatreAutomation, RelayFunction
|
|
|
|
theatre = TheatreAutomation()
|
|
theatre.connect()
|
|
|
|
# Manual relay control
|
|
theatre.set_relay(RelayFunction.HOUSE_LIGHTS, False) # Lights off
|
|
theatre.set_relay(RelayFunction.SPEAKER_POWER, True) # Speakers on
|
|
|
|
# Run effects
|
|
theatre.effect_sequence("flash")
|
|
theatre.effect_sequence("pulse")
|
|
|
|
# Get status
|
|
status = theatre.get_status()
|
|
print(status)
|
|
|
|
theatre.disconnect()
|
|
```
|
|
|
|
### Web Dashboard Control
|
|
|
|
Add to `dashboard.html`:
|
|
|
|
```html
|
|
<!-- Theatre Automation Controls -->
|
|
<div class="theatre-automation">
|
|
<h2>Theatre Automation</h2>
|
|
|
|
<div class="relay-grid">
|
|
<div class="relay-control">
|
|
<span>House Lights</span>
|
|
<button onclick="controlRelay('HOUSE_LIGHTS', 'on')">ON</button>
|
|
<button onclick="controlRelay('HOUSE_LIGHTS', 'off')">OFF</button>
|
|
</div>
|
|
|
|
<div class="relay-control">
|
|
<span>Speaker Power</span>
|
|
<button onclick="controlRelay('SPEAKER_POWER', 'on')">ON</button>
|
|
<button onclick="controlRelay('SPEAKER_POWER', 'off')">OFF</button>
|
|
</div>
|
|
|
|
<!-- Add more controls for each relay... -->
|
|
</div>
|
|
|
|
<div class="effects">
|
|
<button onclick="runEffect('flash')">Flash Effect</button>
|
|
<button onclick="runEffect('pulse')">Pulse Effect</button>
|
|
<button onclick="runEffect('marquee')">Marquee</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function controlRelay(functionName, state) {
|
|
fetch(`/api/relay/${functionName}/${state}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
console.log(`${functionName} turned ${state}`);
|
|
updateStatus();
|
|
}
|
|
});
|
|
}
|
|
|
|
function runEffect(effectName) {
|
|
fetch(`/api/relay/effect/${effectName}`)
|
|
.then(response => response.json())
|
|
.then(data => console.log('Effect complete'));
|
|
}
|
|
|
|
function updateStatus() {
|
|
fetch('/api/relay/status')
|
|
.then(response => response.json())
|
|
.then(status => {
|
|
// Update UI with relay states
|
|
console.log('Theatre status:', status);
|
|
});
|
|
}
|
|
|
|
// Update status every 5 seconds
|
|
setInterval(updateStatus, 5000);
|
|
</script>
|
|
```
|
|
|
|
## 📋 Automation Sequences
|
|
|
|
### Movie Starting
|
|
1. Wait for light_dim_delay (default 5 seconds)
|
|
2. Turn off house lights
|
|
3. Turn off screen lights (main movie only)
|
|
4. Turn on speaker power
|
|
|
|
### Movie Ending
|
|
1. Turn on house lights
|
|
2. Turn on screen lights
|
|
3. Turn off speaker power (if no other content playing)
|
|
|
|
### Intermission
|
|
1. Turn on house lights
|
|
2. Turn on concession lights
|
|
3. Keep speakers powered
|
|
|
|
### Closing
|
|
1. Turn off speakers
|
|
2. Turn off screen lights
|
|
3. Turn off effect lights
|
|
4. Keep parking and marquee lights on
|
|
|
|
### Emergency
|
|
1. Turn ALL lights ON immediately
|
|
|
|
## ⚙️ Customization
|
|
|
|
### Change Relay Assignments
|
|
|
|
Edit `config.py`:
|
|
|
|
```python
|
|
'relay_mapping': {
|
|
'HOUSE_LIGHTS': 2, # Changed from 1 to 2
|
|
'SCREEN_LIGHTS': 1, # Swapped with house lights
|
|
# ... etc
|
|
}
|
|
```
|
|
|
|
### Adjust Timing
|
|
|
|
```python
|
|
'light_dim_delay': 10, # Wait 10 seconds before dimming
|
|
```
|
|
|
|
### Disable Auto-Features
|
|
|
|
```python
|
|
'auto_theatre_lights': False, # Manual light control only
|
|
'auto_speaker_power': False, # Manual speaker control
|
|
```
|
|
|
|
### Add Custom Functions
|
|
|
|
Edit `theatre_automation.py`:
|
|
|
|
```python
|
|
class RelayFunction(Enum):
|
|
# Add new function
|
|
FOG_MACHINE = 5 # Use relay 5 for fog machine
|
|
|
|
# Add method to control it
|
|
def activate_fog(self, duration=5):
|
|
self.set_relay(RelayFunction.FOG_MACHINE, True)
|
|
time.sleep(duration)
|
|
self.set_relay(RelayFunction.FOG_MACHINE, False)
|
|
```
|
|
|
|
## 🐛 Troubleshooting
|
|
|
|
### ESP32 Not Found
|
|
|
|
**Problem:** "No serial ports found"
|
|
|
|
**Solutions:**
|
|
1. Ensure ESP32 is powered on
|
|
2. Verify Bluetooth pairing
|
|
3. Check Bluetooth COM port in Device Manager (Windows)
|
|
4. Run `hcitool scan` to verify device (Linux)
|
|
|
|
### Connection Fails
|
|
|
|
**Problem:** "Failed to connect to relay controller"
|
|
|
|
**Solutions:**
|
|
1. Check COM port in config matches actual port
|
|
2. Verify no other program is using the port
|
|
3. Try manual port specification: `relay_port: 'COM4'`
|
|
4. Test with standalone script: `python esp32_relay_controller.py`
|
|
|
|
### Relays Don't Respond
|
|
|
|
**Problem:** Commands sent but relays don't switch
|
|
|
|
**Solutions:**
|
|
1. Test ESP32 with Serial Bluetooth Terminal app
|
|
2. Check ESP32 is running and Bluetooth is active
|
|
3. Verify relay module has power
|
|
4. Check wiring between ESP32 and relay module
|
|
|
|
### Lights Don't Dim
|
|
|
|
**Problem:** Automation enabled but lights stay on
|
|
|
|
**Solutions:**
|
|
1. Check `auto_theatre_lights: True` in config
|
|
2. Verify relay wiring to light switch/relay
|
|
3. Check relay polarity (active HIGH vs LOW)
|
|
4. Test manual control: `theatre.set_relay(RelayFunction.HOUSE_LIGHTS, False)`
|
|
|
|
## 📚 API Reference
|
|
|
|
### ESP32RelayController
|
|
|
|
```python
|
|
controller = ESP32RelayController()
|
|
|
|
# Connection
|
|
controller.connect(port='COM4')
|
|
controller.disconnect()
|
|
|
|
# Basic Control
|
|
controller.turn_on(1) # Turn on relay 1
|
|
controller.turn_off(1) # Turn off relay 1
|
|
controller.toggle(1) # Toggle relay 1
|
|
controller.all_on() # All relays on
|
|
controller.all_off() # All relays off
|
|
|
|
# Status
|
|
controller.get_status() # Request status update
|
|
controller.get_relay_state(1) # Get cached state of relay 1
|
|
controller.get_all_states() # Get all cached states
|
|
|
|
# Callbacks
|
|
controller.register_callback('connect', on_connect_func)
|
|
controller.register_callback('disconnect', on_disconnect_func)
|
|
controller.register_callback('status_update', on_status_func)
|
|
```
|
|
|
|
### TheatreAutomation
|
|
|
|
```python
|
|
theatre = TheatreAutomation(config)
|
|
|
|
# Connection
|
|
theatre.connect(port='COM4')
|
|
theatre.disconnect()
|
|
|
|
# Automation Sequences
|
|
theatre.movie_starting(is_trailer=True)
|
|
theatre.movie_ended(is_trailer=False)
|
|
theatre.intermission()
|
|
theatre.closing_sequence()
|
|
theatre.emergency_lights_on()
|
|
|
|
# Manual Control
|
|
theatre.set_relay(RelayFunction.HOUSE_LIGHTS, True)
|
|
theatre.toggle_relay(RelayFunction.EFFECT_LIGHTS)
|
|
theatre.effect_sequence("flash")
|
|
|
|
# Status
|
|
status = theatre.get_status()
|
|
```
|
|
|
|
## 🎬 Example Workflows
|
|
|
|
### Unattended Mode Integration
|
|
|
|
```python
|
|
# In unattend mode cycle handler
|
|
def cycle_content():
|
|
if theatre:
|
|
# Starting trailer
|
|
theatre.movie_starting(is_trailer=True)
|
|
|
|
play_trailer()
|
|
|
|
if theatre:
|
|
theatre.movie_ended(is_trailer=True)
|
|
time.sleep(2) # Brief pause
|
|
theatre.movie_starting(is_trailer=False)
|
|
|
|
play_movie()
|
|
|
|
if theatre:
|
|
theatre.movie_ended(is_trailer=False)
|
|
```
|
|
|
|
### NFC Tag Triggered Effects
|
|
|
|
```python
|
|
# Map NFC tags to lighting effects
|
|
NFC_EFFECTS = {
|
|
'AAAAAAAA': 'flash',
|
|
'BBBBBBBB': 'pulse',
|
|
'CCCCCCCC': 'marquee',
|
|
}
|
|
|
|
def handle_nfc(tag_id):
|
|
if tag_id in NFC_EFFECTS and theatre:
|
|
theatre.effect_sequence(NFC_EFFECTS[tag_id])
|
|
```
|
|
|
|
## 🔐 Safety Notes
|
|
|
|
1. **AC Power:** Only qualified electricians should work with mains voltage
|
|
2. **Fusing:** Use appropriate fuses for all loads
|
|
3. **Ratings:** Don't exceed relay current/voltage ratings
|
|
4. **Failsafe:** Ensure system fails to a safe state (lights ON)
|
|
5. **Testing:** Test thoroughly with low-voltage loads first
|
|
|
|
## 📖 Additional Resources
|
|
|
|
- **ESP32 Relay Repository:** `esp32-bluetooth-relay`
|
|
- **ESP32 Documentation:** Full wiring and setup guide
|
|
- **Python Serial:** https://pyserial.readthedocs.io/
|
|
- **Bluetooth Serial:** https://learn.adafruit.com/android-bluetooth-serial
|
|
|
|
---
|
|
|
|
**Version:** 1.0
|
|
**Last Updated:** December 2025
|
|
**Integration Status:** Ready for testing
|