Initial import
This commit is contained in:
commit
20aaa77701
3 changed files with 475 additions and 0 deletions
216
WIRING.txt
Normal file
216
WIRING.txt
Normal 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
21
config.py
Normal 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
238
main.py
Normal 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")
|
||||||
Loading…
Add table
Reference in a new issue