Add comprehensive ESP32 relay integration guide
This commit is contained in:
560
docs/ESP32_RELAY_INTEGRATION.md
Normal file
560
docs/ESP32_RELAY_INTEGRATION.md
Normal file
@@ -0,0 +1,560 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user