14 KiB
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
- ESP32 Development Board (with Bluetooth)
- 8-Channel Relay Module (5V)
- Wiring components (jumper wires, power supply)
- Computer with Bluetooth (for Python script communication)
Software Required
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:
- Open Bluetooth settings
- Search for "ESP32-Relay-8CH"
- Pair the device
- Note the COM port assigned (e.g., COM4)
Linux:
# 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
cd moonlight-drive-in
pip install pyserial
Step 2: Configure Theatre Automation
Edit config.py and add the relay configuration section:
# 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:
# 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:
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
python esp32_relay_controller.py
This will:
- List available serial ports
- Connect to ESP32
- Run interactive test mode
Test 2: Theatre Automation
python theatre_automation.py
This will:
- Initialize theatre automation
- Run a demo sequence (trailer → movie → closing)
- Test all automation functions
Test 3: Integration Test
- Start the theatre software normally
- Watch for log messages: "Theatre automation enabled"
- Scan an NFC tag to start a movie
- Observe lights dimming after delay
- Movie ends and lights come back up
🎮 Manual Control
Command Line Control
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:
<!-- 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
- Wait for light_dim_delay (default 5 seconds)
- Turn off house lights
- Turn off screen lights (main movie only)
- Turn on speaker power
Movie Ending
- Turn on house lights
- Turn on screen lights
- Turn off speaker power (if no other content playing)
Intermission
- Turn on house lights
- Turn on concession lights
- Keep speakers powered
Closing
- Turn off speakers
- Turn off screen lights
- Turn off effect lights
- Keep parking and marquee lights on
Emergency
- Turn ALL lights ON immediately
⚙️ Customization
Change Relay Assignments
Edit config.py:
'relay_mapping': {
'HOUSE_LIGHTS': 2, # Changed from 1 to 2
'SCREEN_LIGHTS': 1, # Swapped with house lights
# ... etc
}
Adjust Timing
'light_dim_delay': 10, # Wait 10 seconds before dimming
Disable Auto-Features
'auto_theatre_lights': False, # Manual light control only
'auto_speaker_power': False, # Manual speaker control
Add Custom Functions
Edit theatre_automation.py:
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:
- Ensure ESP32 is powered on
- Verify Bluetooth pairing
- Check Bluetooth COM port in Device Manager (Windows)
- Run
hcitool scanto verify device (Linux)
Connection Fails
Problem: "Failed to connect to relay controller"
Solutions:
- Check COM port in config matches actual port
- Verify no other program is using the port
- Try manual port specification:
relay_port: 'COM4' - Test with standalone script:
python esp32_relay_controller.py
Relays Don't Respond
Problem: Commands sent but relays don't switch
Solutions:
- Test ESP32 with Serial Bluetooth Terminal app
- Check ESP32 is running and Bluetooth is active
- Verify relay module has power
- Check wiring between ESP32 and relay module
Lights Don't Dim
Problem: Automation enabled but lights stay on
Solutions:
- Check
auto_theatre_lights: Truein config - Verify relay wiring to light switch/relay
- Check relay polarity (active HIGH vs LOW)
- Test manual control:
theatre.set_relay(RelayFunction.HOUSE_LIGHTS, False)
📚 API Reference
ESP32RelayController
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
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
# 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
# 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
- AC Power: Only qualified electricians should work with mains voltage
- Fusing: Use appropriate fuses for all loads
- Ratings: Don't exceed relay current/voltage ratings
- Failsafe: Ensure system fails to a safe state (lights ON)
- 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