Initial import

This commit is contained in:
Chad Mercer 2026-05-30 20:16:43 -05:00
commit 20aaa77701
3 changed files with 475 additions and 0 deletions

216
WIRING.txt Normal file
View file

@ -0,0 +1,216 @@
================================================================================
GARAGE DOOR CONTROLLER - WIRING DIAGRAM
Raspberry Pi Pico 2
================================================================================
HARDWARE REQUIRED
-----------------
- Raspberry Pi Pico 2
- SunFounder 2-Channel 5V Relay Module (Amazon: B00E0NTPP4)
- 2x Door position sensors (optional, for status detection)
- Jumper wires (female-to-male recommended)
- Power supply (5V, 1A minimum)
================================================================================
PIN CONFIGURATION
================================================================================
Pico 2 Pin Layout (Top View)
=============================
| 3V3(1) (2) VBUS |
| GND (3) (4) 3V3_EN|
| GP28 (5) (6) ADC2 |
| GP27 (7) (8) ADC1 |
| GP26 (9) (10) ADC0 |
| RUN (11) (12) GP22 |
| GP21 (13) (14) GP20 |
| GP19 (15) (16) GP18 |
| GP17 (17) (18) GP16 |
| GP15 (19) (20) GP14 |
| GP13 (21) (22) GP12 |
| GND (23) (24) GP11 |
| VSYS (25) (26) GP10 |
| GND (27) (28) GP9 |
| 3V3 (29) (30) GP8 |
| 3V3 (31) (32) GP7 |
| ADC3 (33) (34) GP6 |
| GP4 (35) (36) GP5 |
| GP3 (37) (38) GP2 |
| GP1 (39) (40) GP0 |
|_____(41) (42)________|
================================================================================
RELAY MODULE WIRING
================================================================================
Relay Module Pins (Top View)
=============================
+--------+ +--------+
| IN1 | | IN2 |
| VCC | | GND |
| GND | | COM2 |
| COM1 | | NO2 |
| NO1 | | NC2 |
| NC1 | | |
+--------+ +--------+
Connect Relay Module to Pico 2:
-------------------------------
Relay Module Pico 2
----------- -------
VCC ----> 5V (Pin 39)
GND ----> GND (Pin 38)
IN1 ----> GP15 (Pin 20) - Door 1
IN2 ----> GP16 (Pin 21) - Door 2
================================================================================
GARAGE DOOR OPENER WIRING
================================================================================
OVERHEAD DOOR ODYSSEY 1000 TERMINALS
------------------------------------
Locate the wall control terminals on your garage door opener motor unit.
These are typically labeled "WALL CONTROL" or have two screw terminals
(usually red and white wires).
WIRING DIAGRAM FOR EACH DOOR:
-----------------------------
+-------------------+ +-------------------+
| Garage Door | | Garage Door |
| Opener #1 | | Opener #2 |
| | | |
| Red Wire ----+--+---------+--+--> Relay 1 COM |
| | | | | |
| White Wire --+--+---------+--+--> Relay 1 NO |
| (parallel w/ | |
| wall button) | |
+-------------------+ +-------------------+
|
v
+-------------------+ +-------------------+
| | | |
| Red Wire ----+--+---------+--+--> Relay 2 COM |
| | | | | |
| White Wire --+--+---------+--+--> Relay 2 NO |
| (parallel w/ | |
| wall button) | |
+-------------------+ +-------------------+
IMPORTANT NOTES:
- Wire in PARALLEL with existing wall buttons (keep them functional)
- Use NO (Normally Open) terminals, NOT NC
- Garage door button circuit is low voltage (~12-24V), safe to work with
- Always disconnect power before working with opener wiring
================================================================================
DOOR SENSOR WIRING (OPTIONAL)
================================================================================
If using door position sensors to detect open/closed status:
Sensor 1 (Door 1) Pico 2
--------------- -------
VCC (if powered) --> 3V3 (Pin 36)
GND --> GND (Pin 38)
Signal --> GP14 (Pin 19) - Configured in config.py
Sensor 2 (Door 2) Pico 2
--------------- -------
VCC (if powered) --> 3V3 (Pin 36)
GND --> GND (Pin 38)
Signal --> GP13 (Pin 18) - Configured in config.py
Note: Config uses internal pull-up resistors, so sensors should be
configured as normally-open (closed = door open).
================================================================================
COMPLETE WIRING SUMMARY
================================================================================
+------------------+
| Pico 2 W |
+------------------+
| |
5V (Pin 39) ----+--> VCC (Relay) |
| |
GND (Pin 38) ---+--> GND (Relay) |
| |
GP15 (Pin 20)-->--> IN1 (Relay) |---- Door 1 Control
| |
GP16 (Pin 21)-->--> IN2 (Relay) |---- Door 2 Control
| |
GP14 (Pin 19)-->--> Door Sensor 1 |---- (Optional)
| |
GP13 (Pin 18)-->--> Door Sensor 2 |---- (Optional)
| |
+------------------+
================================================================================
CONFIGURATION
================================================================================
Edit config.py to customize:
WIFI_SSID - Your WiFi network name
WIFI_PASSWORD - Your WiFi password
STATIC_IP - Fixed IP address for the Pico
SUBNET - Subnet mask
GATEWAY - Router IP address
DNS - DNS server IP
RELAY1_PIN - GPIO pin for door 1 relay (default: 15)
RELAY2_PIN - GPIO pin for door 2 relay (default: 16)
DOOR1_SENSOR_PIN - GPIO pin for door 1 sensor (default: 14)
DOOR2_SENSOR_PIN - GPIO pin for door 2 sensor (default: 13)
PRESS_COOLDOWN - Seconds between button presses (default: 2)
================================================================================
SAFETY FEATURES
================================================================================
1. PRESS COOLDOWN
- Prevents rapid repeated presses
- 2-second cooldown between toggles
- Displays warning message if pressed too quickly
2. PARALLEL WIRING
- Original wall buttons remain functional
- No loss of manual control
3. LOW-VOLTAGE OPERATION
- Relay contacts handle the garage door opener's low-voltage circuit
- No mains voltage involved in control circuit
================================================================================
TESTING PROCEDURE
================================================================================
1. Upload config.py and main.py to Pico 2
2. Wire relay module to Pico (5V, GND, GP15, GP16)
3. Connect relay to one garage door opener (test with door 1 first)
4. Power on Pico and check serial output for IP address
5. Navigate to http://<STATIC_IP> in browser
6. Test toggle button - door should respond
7. Verify wall button still works
8. Repeat for door 2
================================================================================
FAQ
================================================================================
Q: The relay clicks but the door doesn't respond
A: Check wiring - ensure COM and NO terminals are used, not NC
Q: Door status shows wrong
A: Check sensor wiring or disable sensors in code (remove sensor pins)
Q: Can't connect to web interface
A: Verify STATIC_IP is in your network's subnet and not conflicting
Q: Button press has no effect
A: Verify RELAY_ON/RELAY_OFF values match your relay (try swapping 0/1)
================================================================================

21
config.py Normal file
View file

@ -0,0 +1,21 @@
WIFI_SSID = "24isenough"
WIFI_PASSWORD = "UltimateDrums!"
STATIC_IP = "192.168.1.63"
SUBNET = "255.255.255.0"
GATEWAY = "192.168.1.254"
DNS = "192.168.1.254"
RELAY1_PIN = 15
RELAY2_PIN = 16
DOOR1_SENSOR_PIN = 14
DOOR2_SENSOR_PIN = 13
RELAY_ON = 0
RELAY_OFF = 1
DOOR_OPEN = 1
DOOR_CLOSED = 0
PRESS_COOLDOWN = 2

238
main.py Normal file
View file

@ -0,0 +1,238 @@
import network
import socket
import time
import machine
import config
from machine import Pin
relay1 = Pin(config.RELAY1_PIN, Pin.OUT)
relay2 = Pin(config.RELAY2_PIN, Pin.OUT)
door_sensor1 = Pin(config.DOOR1_SENSOR_PIN, Pin.IN, Pin.PULL_UP)
door_sensor2 = Pin(config.DOOR2_SENSOR_PIN, Pin.IN, Pin.PULL_UP)
relay1.value(config.RELAY_OFF)
relay2.value(config.RELAY_OFF)
last_press_time = {"door1": 0, "door2": 0}
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(config.WIFI_SSID, config.WIFI_PASSWORD)
max_retries = 30
while not wlan.isconnected() and max_retries > 0:
time.sleep(1)
max_retries -= 1
if wlan.isconnected():
wlan.ifconfig((config.STATIC_IP, config.SUBNET, config.GATEWAY, config.DNS))
ip_address = config.STATIC_IP
else:
ip_address = None
def toggle_door(door_num):
current_time = int(time.time())
door_key = f"door{door_num}"
if current_time - last_press_time[door_key] < config.PRESS_COOLDOWN:
return False
last_press_time[door_key] = current_time
if door_num == 1:
relay1.value(config.RELAY_ON)
else:
relay2.value(config.RELAY_ON)
time.sleep(0.5)
if door_num == 1:
relay1.value(config.RELAY_OFF)
else:
relay2.value(config.RELAY_OFF)
return True
def get_door_status(door_num):
if door_num == 1:
sensor = door_sensor1
else:
sensor = door_sensor2
return "open" if sensor.value() == config.DOOR_OPEN else "closed"
def get_html(status1, status2, msg="", client_ip=""):
return (
"""<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Garage Door Control</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 30px; background-color: #f5f5f5; }
.door-section { background: white; padding: 20px; margin: 20px auto; max-width: 400px; border-radius: 10px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
.status { font-size: 20px; margin: 15px; padding: 15px; border-radius: 8px; }
.open { background-color: #90EE90; }
.closed { background-color: #FFB6C1; }
button { font-size: 18px; padding: 15px 30px; cursor: pointer; background-color: #4CAF50; color: white; border: none; border-radius: 5px; }
button:hover { background-color: #45a049; }
button:disabled { background-color: #cccccc; cursor: not-allowed; }
.ip { margin-top: 30px; color: #666; }
.message { color: #ff6600; font-size: 16px; margin: 10px; }
.log-section {
background: white;
padding: 20px;
margin: 30px auto;
max-width: 600px;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
height: 250px;
display: flex;
flex-direction: column;
}
.log-section h2 { margin-top: 0; }
.log-box {
flex: 1;
width: 100%;
font-family: monospace;
font-size: 12px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
resize: none;
background-color: #1e1e1e;
color: #00ff00;
box-sizing: border-box;
overflow-y: scroll;
text-align: left;
}
.log-entry { margin: 2px 0; }
</style>
</head>
<body>
<h1>Garage Door Control</h1>
<div class="door-section">
<h2>Door 1</h2>
<div class="status """
+ status1
+ """">
Door is """
+ status1.upper()
+ """
</div>
<form method="POST">
<button type="submit" name="action" value="toggle1">Toggle Door 1</button>
</form>
</div>
<div class="door-section">
<h2>Door 2</h2>
<div class="status """
+ status2
+ """">
Door is """
+ status2.upper()
+ """
</div>
<form method="POST">
<button type="submit" name="action" value="toggle2">Toggle Door 2</button>
</form>
</div>
"""
+ ('<div class="message">' + msg + "</div>" if msg else "")
+ """
<div class="log-section">
<h2>Activity Log</h2>
<textarea id="logBox" class="log-box" readonly></textarea>
</div>
<div class="ip">IP: """
+ (ip_address or "Not connected")
+ """</div>
<script>
var clientIP = \""""
+ client_ip
+ """\";
function loadLog() {
var log = localStorage.getItem('garageDoorLog') || '';
document.getElementById('logBox').value = log;
document.getElementById('logBox').scrollTop = document.getElementById('logBox').scrollHeight;
}
function addLogEntry(door, ip) {
var now = new Date();
var timestamp = now.toLocaleString();
var entry = '[' + timestamp + '] Door ' + door + ' toggled from ' + ip + '\\n';
var log = localStorage.getItem('garageDoorLog') || '';
log = entry + log;
var lines = log.split('\\n').filter(function(line) { return line.trim(); });
if (lines.length > 100) {
log = lines.slice(0, 100).join('\\n') + '\\n';
}
localStorage.setItem('garageDoorLog', log);
loadLog();
}
loadLog();
document.querySelectorAll('form').forEach(function(form) {
form.addEventListener('submit', function() {
var action = form.querySelector('button').value;
var doorNum = action.replace('toggle', '');
addLogEntry(doorNum, clientIP);
});
});
</script>
</body>
</html>"""
)
def web_server():
addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)
while True:
try:
cl, addr = s.accept()
client_ip = addr[0]
request = cl.recv(1024)
request_str = request.decode("utf-8")
message = ""
if "POST" in request_str:
if "toggle1" in request_str:
if toggle_door(1):
message = ""
else:
message = "Please wait before pressing again"
elif "toggle2" in request_str:
if toggle_door(2):
message = ""
else:
message = "Please wait before pressing again"
status1 = get_door_status(1)
status2 = get_door_status(2)
response = get_html(status1, status2, message, client_ip)
cl.send("HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\n")
cl.send(response)
cl.close()
except Exception as e:
pass
if __name__ == "__main__":
if ip_address:
print("Connected, IP:", ip_address)
web_server()
else:
print("Failed to connect to WiFi")